MultiSelect
Select multiple items from a list. Stores the value field of each selected option and displays the human-readable label as chips. Supports static arrays and async search functions.
Import
import { MultiSelect } from '@atom-forge/ui';
import type { SelectOption, SelectOptionsSource } from '@atom-forge/ui';
Basic usage
<script lang="ts">
import { MultiSelect } from '@atom-forge/ui';
const options = [
{ value: 'hu', label: 'Hungary' },
{ value: 'de', label: 'Germany' },
{ value: 'fr', label: 'France' },
];
let value = $state<(string | number)[]>([]);
</script>
<MultiSelect {options} bind:value placeholder="Select countries..." />
Props
| Prop |
Type |
Default |
Description |
value |
(string | number)[] |
[] |
Selected values. Use bind:value for two-way binding. |
options |
SelectOptionsSource |
— |
Static array or async source object with search and get methods. |
placeholder |
string |
'Select...' |
Placeholder shown when nothing is selected. |
disabled |
boolean |
false |
Disables the trigger. Chips remain visible. |
searchable |
boolean |
true |
Shows a search input in the dropdown. |
button |
SelectButtonTrigger |
— |
Renders a Button component as the trigger instead of the chip field. |
hideKeyboardHints |
boolean |
false |
Hides the keyboard-navigation hint bar at the bottom of the dropdown. |
clearable |
boolean |
false |
Shows a clear-all button when items are selected. |
sortable |
boolean |
false |
Enables drag-and-drop chip reordering. |
max |
number |
— |
Maximum selectable items. Further options are greyed out. |
compact |
boolean |
— |
Compact size. Mutually exclusive with small. |
small |
boolean |
— |
Small size. Mutually exclusive with compact. |
class |
string |
— |
Extra classes forwarded to the trigger element. |
Snippets
chip — custom chip renderer
{#snippet chip(opt, remove)}
<span>{opt.label} <button onclick={remove}>×</button></span>
{/snippet}
Receives (opt: SelectOption, remove: () => void).
option — custom dropdown item renderer
{#snippet option(opt, isHighlighted)}
<div class={isHighlighted ? 'bg-accent' : ''}>
{opt.label}
</div>
{/snippet}
Receives (opt: SelectOption, isHighlighted: boolean).
Async options
Pass an object with search and get methods as options.
search(query) fetches results on demand.
get(values) resolves selected labels in sortable mode. Non-sortable mode currently caches only options selected in the dropdown; see Setup and behavior below.
<script lang="ts">
import { MultiSelect, type SelectOption } from '@atom-forge/ui';
const source = {
search: async (query: string): Promise<SelectOption[]> => {
const res = await fetch(`/api/users?q=${encodeURIComponent(query)}`);
return res.json();
},
get: async (values: (string | number)[]): Promise<SelectOption[]> => {
const res = await fetch(`/api/users?ids=${values.join(',')}`);
return res.json();
},
};
let selected = $state<(string | number)[]>([]);
</script>
<MultiSelect options={source} bind:value={selected} placeholder="Search users…" />
Sortable chips
Add sortable to enable drag-and-drop reordering. The value binding is kept in sync with the displayed order.
<MultiSelect {options} bind:value sortable />
Use the same button prop as Select when the trigger should be a Button. No chevron is shown unless you provide endIcon.
Variant flags remain optional booleans, including dynamic values and explicit false. The trigger forwards at most one active variant to Button: the first truthy flag in the order destructive → secondary → ghost → link → muted → accent. If none is truthy, it uses the primary style. Variant changes are reactive.
<MultiSelect
{options}
bind:value
button={{label: 'Add tags', icon: Plus, secondary: true}}
/>
Keyboard
| Key |
Action |
ArrowDown / Up |
Navigate the dropdown list |
Enter |
Toggle the highlighted option |
Escape |
Close the dropdown |
No option is highlighted when the dropdown opens. Press an arrow key to begin keyboard navigation.
SelectOption type
type SelectOption = {
value: string | number;
label: any;
};
When to use
Use to choose multiple string or numeric IDs and optionally reorder selected chips.
Alternatives
Use Select for one ID or TagEditor for free-form strings instead of an ID/label model.
Setup and behavior
Render below Root for popup context. Bind an array of unique IDs with consistent string/number types. Toggling updates selection without closing the dropdown; there is no public change callback. The open dropdown snapshots the initial selection rather than tracking later external assignments. max prevents additions but does not trim externally supplied values. Static, non-sortable chips follow options-array order, not selected-value order. Async search has no built-in debounce. Current limitation: non-sortable async mode displays only cached options selected in the dropdown; it does not call get() to hydrate preselected labels. Sortable mode calls get(value) but synchronizes resolved items back into the value array, so unresolved IDs may be dropped. Do not rely on it to preserve unavailable IDs.
MultiSelect
Select multiple items from a list. Stores the
valuefield of each selected option and displays the human-readablelabelas chips. Supports static arrays and async search functions.Import
import { MultiSelect } from '@atom-forge/ui'; import type { SelectOption, SelectOptionsSource } from '@atom-forge/ui';Basic usage
<script lang="ts"> import { MultiSelect } from '@atom-forge/ui'; const options = [ { value: 'hu', label: 'Hungary' }, { value: 'de', label: 'Germany' }, { value: 'fr', label: 'France' }, ]; let value = $state<(string | number)[]>([]); </script> <MultiSelect {options} bind:value placeholder="Select countries..." />Props
value(string | number)[][]bind:valuefor two-way binding.optionsSelectOptionsSourcesearchandgetmethods.placeholderstring'Select...'disabledbooleanfalsesearchablebooleantruebuttonSelectButtonTriggerhideKeyboardHintsbooleanfalseclearablebooleanfalsesortablebooleanfalsemaxnumbercompactbooleansmall.smallbooleancompact.classstringSnippets
chip— custom chip renderer{#snippet chip(opt, remove)} <span>{opt.label} <button onclick={remove}>×</button></span> {/snippet}Receives
(opt: SelectOption, remove: () => void).option— custom dropdown item renderer{#snippet option(opt, isHighlighted)} <div class={isHighlighted ? 'bg-accent' : ''}> {opt.label} </div> {/snippet}Receives
(opt: SelectOption, isHighlighted: boolean).Async options
Pass an object with
searchandgetmethods asoptions.search(query)fetches results on demand.get(values)resolves selected labels in sortable mode. Non-sortable mode currently caches only options selected in the dropdown; see Setup and behavior below.<script lang="ts"> import { MultiSelect, type SelectOption } from '@atom-forge/ui'; const source = { search: async (query: string): Promise<SelectOption[]> => { const res = await fetch(`/api/users?q=${encodeURIComponent(query)}`); return res.json(); }, get: async (values: (string | number)[]): Promise<SelectOption[]> => { const res = await fetch(`/api/users?ids=${values.join(',')}`); return res.json(); }, }; let selected = $state<(string | number)[]>([]); </script> <MultiSelect options={source} bind:value={selected} placeholder="Search users…" />Sortable chips
Add
sortableto enable drag-and-drop reordering. Thevaluebinding is kept in sync with the displayed order.<MultiSelect {options} bind:value sortable />Button trigger
Use the same
buttonprop as Select when the trigger should be a Button. No chevron is shown unless you provideendIcon.Variant flags remain optional booleans, including dynamic values and explicit
false. The trigger forwards at most one active variant to Button: the first truthy flag in the orderdestructive→secondary→ghost→link→muted→accent. If none is truthy, it uses the primary style. Variant changes are reactive.<MultiSelect {options} bind:value button={{label: 'Add tags', icon: Plus, secondary: true}} />Keyboard
ArrowDown / UpEnterEscapeNo option is highlighted when the dropdown opens. Press an arrow key to begin keyboard navigation.
SelectOption type
type SelectOption = { value: string | number; label: any; };When to use
Use to choose multiple string or numeric IDs and optionally reorder selected chips.
Alternatives
Use Select for one ID or TagEditor for free-form strings instead of an ID/label model.
Setup and behavior
Render below
Rootfor popup context. Bind an array of unique IDs with consistent string/number types. Toggling updates selection without closing the dropdown; there is no public change callback. The open dropdown snapshots the initial selection rather than tracking later external assignments.maxprevents additions but does not trim externally supplied values. Static, non-sortable chips follow options-array order, not selected-value order. Async search has no built-in debounce. Current limitation: non-sortable async mode displays only cached options selected in the dropdown; it does not callget()to hydrate preselected labels. Sortable mode callsget(value)but synchronizes resolved items back into the value array, so unresolved IDs may be dropped. Do not rely on it to preserve unavailable IDs.