Multi Select ported
A choice field that collects several values as chips.
A labelled trigger that opens a multi-choice menu. Picks collect as chips with an overflow count, and a clear button empties the field. Built on Bits UI Combobox multiple mode with ARC styling verbatim.
Loading demo…
Guidance
When to use
- Form fields where several options apply at once, such as tags or team members.
- Compact filters where the selection must stay visible as chips.
- Menus that need accessible multi-select semantics with keyboard navigation.
When not to use
- Use select or combobox when only one value can be chosen.
- Use chip-group when every choice should stay visible as toggleable filters.
- Use checkbox lists when each option needs its own visible label and description.
pnpm add sveltearcUsage
<script lang="ts">
import { MultiSelect } from '$lib/components/multi-select';
let topics = $state(['svelte']);
const options = [
{ value: 'svelte', label: 'Svelte' },
{ value: 'react', label: 'React' },
{ value: 'vue', label: 'Vue', disabled: true }
];
</script>
<MultiSelect
label="Topics"
bind:value={topics}
{options}
description="Followed topics for the weekly digest."
/>Variants & Examples
Controlled with Description
Two-way binding via bind:value with helper text below the field.
<MultiSelect
label="Topics"
bind:value={topics}
options={topicOptions}
description="Followed topics for the weekly digest."
/>Overflow Count
Chips beyond maxVisible fold behind a "+N" count.
<MultiSelect
label="Topics"
bind:value={topics}
options={topicOptions}
maxVisible={1}
/>Disabled
Disables the whole field, or individual options via disabled on the option.
<MultiSelect
label="Locked Topics"
value={['svelte']}
disabled
options={topicOptions}
/>API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | – | Accessible name of the field, rendered as the visible label. |
options | MultiSelectOption[] | – | Array of { value: string; label: string; disabled?: boolean } items in list order. |
value | string[] | undefined | Controlled selection; supports two-way bind:value. |
defaultValue | string[] | [] | Initial selection when uncontrolled. |
onValueChange | (value: string[]) => void | – | Callback fired whenever the selection changes. |
open | boolean | undefined | Controlled open state of the menu; supports two-way bind:open. |
onOpenChange | (open: boolean) => void | – | Callback fired when the menu opens or closes. |
placeholder | string | 'Select options' | Placeholder copy shown when nothing is selected. |
description | string | – | Helper copy rendered below the field. |
maxVisible | number | 2 | Chips shown before the rest fold behind a "+N" count. |
disabled | boolean | false | Whether the whole field is disabled. |
class | string | – | Additional CSS class applied to the field wrapper. |
Keyboard Interactions
| Key | Action |
|---|---|
Enter / Space | Opens the menu from the trigger, or picks the highlighted option when open. |
ArrowDown / ArrowUp | Moves the highlight between enabled options. |
Escape | Closes the menu without changing the selection. |
Accessibility
- Built on Bits UI Combobox multiple mode: the menu carries role="listbox" with aria-multiselectable, options carry role="option" with aria-selected.
- The trigger announces the selection through an aria-labelledby pair of visible label and screen-reader value text.
- Highlight is tracked for the data-active style hook without taking focus from the trigger.
- Disabled options are exposed with aria-disabled and skipped by selection.
Motion
Phase 1 still-state: chips and the overflow count render at their resting end-state with no slot-spring travel; the menu mounts when open with no enter/exit travel; the check renders fully drawn. Phase 2 wires the chip slot springs, overflow count roll, checkbox path draw, and menu spring. Reduced motion keeps the same end-states with instant transitions.
Notes for AI
- Pick when several values can be chosen and should read back as chips.
- Two-way binding is available via bind:value={selected}.
- Control chip overflow with maxVisible; the rest fold behind a "+N" count.
Source of truth: registry/components/multi-select/multi-select.tsx