Selection
Multi Select
Several values from a list, in one field.
import { MultiSelect } from 'omaris' Examples
Basic
Type to filter, Enter to take one, Backspace on an empty box to give it back.
<script lang="ts"> import { MultiSelect } from 'omaris'; let labels = $state(['bug', 'docs']); const LABELS = [ { value: 'bug', label: 'Bug', description: "Something isn't working" }, { value: 'feature', label: 'Feature', description: 'A new capability' }, { value: 'docs', label: 'Documentation', keywords: ['readme', 'guide'] }, { value: 'design', label: 'Design' }, { value: 'a11y', label: 'Accessibility', keywords: ['aria'] } ];</script><div class="w-80 max-w-full"> <MultiSelect options={LABELS} bind:values={labels} label="Labels" placeholder="Pick a few" /></div> Groups
Grouped rows, a "Select all" header and a cap the counter tracks.
<script lang="ts"> import { MultiSelect } from 'omaris'; let reviewers = $state<string[]>(['ada']); const PEOPLE = [ { value: 'ada', label: 'Ada Lovelace', group: 'Engineering' }, { value: 'grace', label: 'Grace Hopper', group: 'Engineering' }, { value: 'alan', label: 'Alan Turing', group: 'Engineering' }, { value: 'katherine', label: 'Katherine Johnson', group: 'Research' }, { value: 'mary', label: 'Mary Jackson', group: 'Research' } ];</script><div class="w-80 max-w-full"> <MultiSelect options={PEOPLE} bind:values={reviewers} label="Reviewers" placeholder="Search people" max={3} selectAll /></div> Narrow
Two answers to a field too narrow for chips: collapse after the second, or drop them for a count.
<script lang="ts"> import { MultiSelect } from 'omaris'; const LABELS = [ { value: 'bug', label: 'Bug' }, { value: 'feature', label: 'Feature' }, { value: 'docs', label: 'Documentation' }, { value: 'design', label: 'Design' } ]; let collapsed = $state(['bug', 'feature', 'docs']); let counted = $state(['bug', 'feature', 'docs']);</script><div class="flex w-64 max-w-full flex-col gap-4"> <MultiSelect options={LABELS} bind:values={collapsed} collapse={2} label="Collapsed" /> <MultiSelect options={LABELS} bind:values={counted} display="count" label="Counted" /></div> Creatable
Nothing matches what was typed, so the field offers to add it.
<script lang="ts"> import { MultiSelect } from 'omaris'; let tags = $state(['svelte']);</script><div class="w-80 max-w-full"> <MultiSelect options={[ { value: 'svelte', label: 'svelte' }, { value: 'tailwind', label: 'tailwind' }, { value: 'typescript', label: 'typescript' } ]} bind:values={tags} creatable label="Tags" placeholder="Type to add" /></div> Overridden
Square accent chips, a tinted list and a taller frame.
<script lang="ts"> import { MultiSelect } from 'omaris'; let values = $state(['bug', 'docs']);</script><div class="w-80 max-w-full"> <MultiSelect options={[ { value: 'bug', label: 'Bug' }, { value: 'docs', label: 'Documentation' }, { value: 'design', label: 'Design' } ]} bind:values label="Overridden" classes={{ chip: 'rounded-sm bg-primary-container text-primary-container-foreground', surface: 'bg-surface-container-low', field: 'min-h-14' }} /></div> When to use it
Use it for
- Several values from a known list in one field: labels on an issue, reviewers on a pull request, categories on a product, permissions on a role.
- A filter that means "any of these": statuses, owners, tags.
- Free-form tags. Use
creatablewhen the list is a head start and new values are expected. - A capped set.
max={3}disables the remaining rows and shows the count under the field, so the limit is visible before it is hit.
Not for
- One value → Select, or Combobox when the list needs searching.
- Two to five options with room to show them all → Checkbox in a group. Hiding four options behind a click is worse than showing them.
- A filter bar above a list where the choices should stay on screen → Chip with
selectable. - Picking from a very long remote list, one at a time → Combobox with
load.
Do
- Give the options
keywordsfor the words people type: an ISO code, a nickname, an old name. Usegroupwhen the list has natural sections. - Pick the
displayto fit the field:chipswhen it is wide,collapse={2}when it is not,countin a narrow filter bar. - Add
selectAllwhen picking most of the list is common, andmaxwhen the form has a real limit. - Keep
closeOnSelectoff. Closing after each pick turns three choices into three round-trips. - Set
nameto post the values. One hidden input per value, so the form sends an array.
Don't
- Use it with two options. That is two checkboxes.
- Rely on chips alone in a narrow field. Five chips in a 16rem input wrap into a wall; use
collapseordisplay="count". - Turn
searchableoff on a list longer than about a dozen. - Let
creatablewrite to the database on every keystroke. Handleoncreateand decide there what a new value means.
Quick reference
size smdefault(default)lg
API
MultiSelect
Several values from a list, in one field.
The field is a text input with the chosen options sitting in front of it, so typing filters and Backspace on an empty query takes the last one back off — the two things that make a tag field feel finished. The list stays open while you pick, because picking several and reopening a menu five times are different jobs.
import { MultiSelect } from 'omaris' <MultiSelect bind:values={tags} {options} label="Labels" max={5} /><MultiSelect bind:values display="count" collapse={2} creatable /> Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
root- Wrapper: label, field, supporting text.
field- The frame: border, background, height, adornment layout.
control- The bare <input>; the frame owns every decoration.
label- No description in the source yet.
adornment- Icon slots at either end of the frame.
affix- Flat text glued to the value — "https://", "kg".
action- An interactive trailing control: reveal, clear.
support- Helper line under the field, and the counter beside it.
counter- No description in the source yet.
surface- No description in the source yet.
option- No description in the source yet.
group- A non-interactive heading above a run of options.
body- No description in the source yet.
description- No description in the source yet.
check- No description in the source yet.
empty- "No results", "Searching…".
highlight- The part of a label that matched what was typed.
chips- The chips and the text input, wrapped inside the frame.
chip- One chosen option — a
Chip, so it is the library's chip and not a lookalike. A quiet tonal tag a little shorter than the frame's line: the field is the thing to read, and the chips are in it. row- A row of the list. The box is the selected state, so the container tint the single-value lists give a chosen row is dropped — three chosen rows in a row read as one grey block, and the box already said it.
box- The tick beside an option in the list — a
Checkbox, for the same reason. all- The "Select all" row above the options.
divider- The hairline between "Select all" and the options.
count- "3 selected", and the "+2" chip that stands for the collapsed ones.
search- The bare text input the chips sit in front of.
chevron- The ▾ at the end of the frame — a button, so it closes the list as well as opening it.
Props
values bindableDefaults to []
string[] The chosen values, in the order they were picked. Bindable.
options Defaults to []
MultiSelectOption[] The options. Rows with a group are filed under that heading.
query bindableDefaults to ''
string What's in the search box. Bindable.
variant Defaults to 'outline'
InputVariant size Defaults to 'default'
InputSize smdefaultlg
display Defaults to 'chips'
MultiSelectDisplay How the chosen options read in the closed field.
collapse number Show only this many chips, and a "+n" for the rest.
max number Most that can be chosen. The remaining rows go disabled at the cap.
searchable Defaults to true
boolean Let the field filter as you type. On by default.
mobileSheet boolean On a phone, the list rises from the bottom edge as a sheet — full width, a scrim behind, dragged down to dismiss, rows a thumb can hit — instead of hanging under the field. false keeps it anchored.
creatable Defaults to false
boolean Offer the typed text as a new value when nothing matches it.
filter (option: MultiSelectOption, query: string) => boolean Your own matcher, when label and keywords aren't enough.
highlight Defaults to true
boolean Bolden the matched part of each label.
clearable Defaults to true
boolean Show an × that empties the field.
closeOnSelect Defaults to false
boolean Close the list after each pick. Off by default — this is a multi-select.
selectAll Defaults to false
boolean A "Select all" / "Clear" row above the options.
emptyText Defaults to 'No results'
string label string supportingText string Helper line under the field. Becomes the error text when invalid.
invalid boolean Inside a Field, defaults to the field's.
required boolean Inside a Field, defaults to the field's.
onchange (values: string[], options: MultiSelectOption[]) => void Fires with the new list on every change.
oncreate (label: string) => MultiSelectOption | void Called when a creatable field takes text that matched nothing.
start Snippet Leading icon inside the frame.
option Snippet<[MultiSelectOption, { selected: boolean; active: boolean; query: string }]> Replaces a row of the list.
chip Snippet<[MultiSelectOption, { remove: () => void }]> Replaces a chip in the field.
class string classes MultiSelectClasses Per-part Tailwind overrides. class still covers the root.
ref bindableDefaults to null
HTMLInputElement | null