Forms
Mention input
For comments and notes where people are tagged. Type @ and matching names appear right at the caret; each mention is tinted, deleted in one go, and handed back as an id.
Installation
npx sinaui add mention-inputCopy src/lib/ui/MentionInput.svelte, with Avatar.svelte, announce.ts, floating.ts, glide.ts, motion.ts, spring.ts, scroll-edges.ts and tokens.css. No dependencies.
Examples
From a list
Type @ and a few letters. Enter or Tab puts the name in; Backspace straight after a name removes all of it.
No one by that name
No one mentioned yet
<script lang="ts">
import MentionInput, { type Person } from '#lib/ui/MentionInput.svelte';
const scholars: Person[] = [
{ id: 'ibn-sina', name: 'Ibn Sina', detail: 'Physician, Bukhara' },
{ id: 'khwarizmi', name: 'Al-Khwarizmi', detail: 'Mathematician, Baghdad' },
{ id: 'biruni', name: 'Al-Biruni', detail: 'Astronomer, Ghazni' },
{ id: 'haytham', name: 'Ibn al-Haytham', detail: 'Optics, Cairo' },
{ id: 'fihri', name: 'Fatima al-Fihri', detail: 'Founder, Fez' },
{ id: 'maryam', name: 'Maryam al-Asturlabi', detail: 'Astrolabe maker, Aleppo' }
];
let text = $state('');
let mentioned = $state<string[]>([]);
</script>
<div class="demo">
<MentionInput
label="Note for the reading room"
people={scholars}
bind:value={text}
bind:mentions={mentioned}
placeholder="Type @ to mention someone"
/>
<p class="out" role="status">
{mentioned.length
? `Will notify: ${mentioned.map((id) => scholars.find((s) => s.id === id)?.name).join(', ')}`
: 'No one mentioned yet'}
</p>
</div>
<style>
.demo {
inline-size: min(28rem, 100%);
}
.out {
margin: 0.5rem 0 0;
color: var(--ui-muted);
font-size: 0.8125rem;
}
</style>From your server
Pass search to look people up as you type; only the latest answer is shown.
No one by that name
<script lang="ts">
import MentionInput, { type Person } from '#lib/ui/MentionInput.svelte';
// Stands in for your API: a short wait, then the matches.
const staff: Person[] = [
{ id: 'hunayn', name: 'Hunayn ibn Ishaq', detail: 'Head translator' },
{ id: 'thabit', name: 'Thabit ibn Qurra', detail: 'Mathematics' },
{ id: 'banu-musa', name: 'Banu Musa', detail: 'Instruments' },
{ id: 'kindi', name: 'Al-Kindi', detail: 'Philosophy' }
];
async function search(q: string) {
await new Promise((r) => setTimeout(r, 300));
const f = q.toLowerCase();
return staff.filter((p) => p.name.toLowerCase().includes(f));
}
</script>
<div class="demo">
<MentionInput label="Reply" {search} rows={2} placeholder="@ to mention the translators" />
</div>
<style>
.demo {
inline-size: min(28rem, 100%);
}
</style>Props
| Prop | Type | Default |
|---|---|---|
label | string (required) | |
value | string (bindable) | '' |
mentions | string[]: ids of the people in the text (bindable) | [] |
people | { id, name, detail?, avatar? }[] | [] |
search | (query) => Promise<Person[]> | |
rows | number: starting height in lines | 3 |
placeholder, hint, error, name | string | |
disabled | boolean | false |
empty | string | 'No one by that name' |
Keyboard
| Key | Does |
|---|---|
| @ | Starts a mention and shows suggestions |
| ↑ ↓ | Moves through the suggestions |
| Enter Tab | Puts the highlighted name in |
| Escape | Closes the suggestions and keeps typing plain text |
| Backspace | Straight after a mention, removes the whole name |
Accessibility
- The text box points at the highlighted suggestion, so screen readers read it as you move.
- The number of matches is announced, and so is each name added or removed.
- Names are plain text in the field: copying and pasting keeps them readable.