Forms
Multi select
Several choices from a fixed list, kept readable in the field as chips. The list searches as you type and stays open while you tick.
Installation
npx sinaui add multi-selectCopy src/lib/ui/MultiSelect.svelte, with Icon.svelte, announce.ts, floating.ts, glide.ts, motion.ts, spring.ts, scroll-edges.ts and tokens.css. No dependencies.
Examples
Basic
Open it, search, tick as many as you like. Backspace in the field removes the last chip.
- Astronomy
- Optics
Astronomy
Medicine
Mathematics
Optics
Philosophy
Geography
Alchemy
Music theory
Choose the collections to include in the catalog.
<script lang="ts">
import MultiSelect, { type MultiOption } from '#lib/ui/MultiSelect.svelte';
const fields: MultiOption[] = [
{ value: 'astronomy', label: 'Astronomy' },
{ value: 'medicine', label: 'Medicine' },
{ value: 'mathematics', label: 'Mathematics' },
{ value: 'optics', label: 'Optics' },
{ value: 'philosophy', label: 'Philosophy' },
{ value: 'geography', label: 'Geography' },
{ value: 'alchemy', label: 'Alchemy' },
{ value: 'music', label: 'Music theory' }
];
let chosen = $state(['astronomy', 'optics']);
</script>
<div class="demo">
<MultiSelect
label="Fields of study"
options={fields}
bind:value={chosen}
hint="Choose the collections to include in the catalog."
/>
</div>
<style>
.demo {
inline-size: min(24rem, 100%);
}
</style>Up to a limit, in a form
max stops at three; the rest wait until one is removed. With name, each choice is sent with the form.
<script lang="ts">
import MultiSelect, { type MultiOption } from '#lib/ui/MultiSelect.svelte';
const translators: MultiOption[] = [
{ value: 'hunayn', label: 'Hunayn ibn Ishaq', description: 'Greek and Syriac' },
{ value: 'thabit', label: 'Thabit ibn Qurra', description: 'Greek, mathematics' },
{ value: 'qusta', label: 'Qusta ibn Luqa', description: 'Greek, medicine' },
{ value: 'ishaq', label: 'Ishaq ibn Hunayn', description: 'Greek, philosophy' },
{ value: 'ibn-muqaffa', label: 'Ibn al-Muqaffa', description: 'Middle Persian' },
{
value: 'yahya',
label: 'Yahya ibn al-Bitriq',
description: 'Greek, unavailable',
disabled: true
}
];
let team = $state<string[]>([]);
</script>
<form class="demo" onsubmit={(e) => e.preventDefault()}>
<MultiSelect
label="Translators"
options={translators}
bind:value={team}
max={3}
name="translators"
placeholder="Choose up to three"
/>
<p class="out">Sends: {team.length ? team.join(', ') : 'nothing yet'}</p>
</form>
<style>
.demo {
inline-size: min(24rem, 100%);
}
.out {
margin: 0.5rem 0 0;
color: var(--ui-muted);
font-size: 0.8125rem;
}
</style>Props
| Prop | Type | Default |
|---|---|---|
label | string (required) | |
options | { value, label, description?, disabled? }[] | |
value | string[] (bindable) | [] |
max | number | |
placeholder | string | 'Choose…' |
name | string: sends each choice with the form | |
hint, error | string | |
disabled | boolean | false |
searchLabel, empty | string | 'Search', 'No matches' |
Keyboard
| Key | Does |
|---|---|
| ↓ Enter Space | Opens the list, with focus in its search box |
| ↑ ↓ | Moves through the options |
| Enter | Ticks or unticks the highlighted option; the list stays open |
| Escape | Closes the list |
| Backspace | In the closed field, removes the last choice |
Accessibility
- The field is a combobox for a multi-select listbox; each option says whether it's chosen.
- Adding and removing are announced with the running count.
- Each chip's remove button is named after it; "Remove all" clears the field.
- With reduced motion, chips appear and leave without scaling.