mentions Svelte Themes

Mentions

Fast, headless mentions/triggers/hashtags/slash-commands library for React, Vue 3, and Svelte 5. ~9KB core, zero dependencies, WAI-ARIA combobox.

@skyastrall/mentions

A tiny, headless, framework-agnostic engine for @mentions, #hashtags, /slash commands, and any custom trigger.

One ~9 KB core. React, Vue 3, and Svelte 5 adapters. Zero runtime dependencies. WAI-ARIA combobox out of the box.

Documentation · Playground · Releases · Changelog


Pick your adapter

Package For Install Version
@skyastrall/mentions-react React 18 / 19 npm i @skyastrall/mentions-react
@skyastrall/mentions-vue Vue 3.4+ npm i @skyastrall/mentions-vue
@skyastrall/mentions-svelte Svelte 5 npm i @skyastrall/mentions-svelte
@skyastrall/mentions-core Any framework / vanilla JS npm i @skyastrall/mentions-core

Every adapter is a thin (~5 KB) wrapper around the same MentionController from core. Pick one, or use them side-by-side in the same monorepo.


Quick start

React
import { Mentions } from "@skyastrall/mentions-react";

const users = [{ id: "1", label: "Alice" }, { id: "2", label: "Bob" }];

<Mentions
  triggers={[{ char: "@", data: users, color: "rgba(99,102,241,0.25)" }]}
  onChange={(markup, plainText) => console.log(markup)}
/>;
Vue 3
<script setup>
import { ref } from "vue";
import { Mentions } from "@skyastrall/mentions-vue";

const users = [{ id: "1", label: "Alice" }, { id: "2", label: "Bob" }];
const markup = ref("");
</script>

<template>
  <Mentions
    :triggers="[{ char: '@', data: users, color: 'rgba(99,102,241,0.25)' }]"
    v-model="markup"
  />
</template>
Svelte 5
<script>
  import { Mentions } from "@skyastrall/mentions-svelte";

  const users = [{ id: "1", label: "Alice" }, { id: "2", label: "Bob" }];
  let markup = $state("");
</script>

<Mentions
  triggers={[{ char: "@", data: users, color: "rgba(99,102,241,0.25)" }]}
  onChange={(m) => (markup = m)}
/>

More patterns — multi-trigger, async data, compound components, ghost text, single-line mode, headless hook/composable/runes — live in the docs.


Three API layers

Every adapter ships the same three layers, so you can match the API to how much control you need.

1. Drop-in component — works without any plumbing.

<Mentions triggers={triggers} onChange={handleChange} />

2. Compound components — own the layout, keep the behavior.

<Mentions triggers={triggers}>
  <Mentions.Editor placeholder="Type @..." />
  <Mentions.Portal>
    <Mentions.List>
      <Mentions.Item render={({ item }) => <UserCard user={item} />} />
    </Mentions.List>
  </Mentions.Portal>
</Mentions>

3. Headless hook / composable / runes — full control. Bring your own UI.

const { editorRef, inputProps, isOpen, items, getItemProps } =
  useMentions({ triggers });

The hook (useMentions in React/Vue, runes-powered in Svelte) is the same shape everywhere — same state, same handlers, same ARIA wiring. Adapters are intentionally thin.


Features

  • Multi-trigger@, #, /, or any character. Per-trigger colors and independent data sources.
  • Contenteditable, DOM-first — cursor handled by the browser. No virtual DOM diffing. No reconciliation tax.
  • Async data — debounce, abort-on-stale, loading states, error surface via onError.
  • Ghost text — AI inline completions. Tab to accept.
  • Single-line mode — 5-layer newline prevention (beforeinput, keydown, paste, drop, sanitize).
  • WAI-ARIA combobox — full keyboard navigation, screen-reader tested (VoiceOver + Chrome).
  • Grammarly / extension defensedata-gramm attributes + node filtering so third-party extensions don't hijack the editor.
  • Controlled & uncontrolled modes.
  • TypeScript-first — generics flow from TriggerConfig<TData> all the way through onSelect.
  • Tiny: ~9 KB core + ~5 KB adapter (gzipped). Zero runtime deps in core.

Why @skyastrall/mentions?

It's headless, not "headless-ish". Zero CSS opinions. No portal magic. You bring the UI.

It's not a full editor. If you need WYSIWYG, formatting toolbars, tables, or images — use Tiptap or Lexical. If you need clean, fast @mentions / #tags / /commands in a textarea-like surface, this is the smallest correct answer.

It's actually multi-framework. One MentionController in core, three thin adapters that all behave identically. Not a React port grudgingly ported to Vue.

No runtime deps in core. The engine runs in Node — pure logic, no jsdom required. That keeps the install graph tiny and the unit tests honest.


Repo layout

packages/
  core/          framework-agnostic engine — MentionController, state machine, parser
  react/         React 18/19 adapter
  vue/           Vue 3.4+ adapter
  svelte/        Svelte 5 adapter (runes)
website/         Astro docs + playground (mentions.skyastrall.com)
playground/      Vite dev sandbox
e2e/             Playwright end-to-end tests

Local development

pnpm install
pnpm build           # build all packages
pnpm test -- --run   # unit tests (Vitest)
pnpm test:e2e        # Playwright
pnpm lint            # Biome

See CONTRIBUTING.md for the full workflow.


License

MIT — built by SkyAstrall.

Top categories

Loading Svelte Themes