Skip to content
omaris

Selection

Multi Select

Several values from a list, in one field.

import { MultiSelect } from 'omaris'
Learn

Examples

Basic

Type to filter, Enter to take one, Backspace on an empty box to give it back.

Bug Documentation
<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.

Ada Lovelace
1/3
<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.

Bug Feature +1
3 selected
<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.

svelte
<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.

Bug Documentation
<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 creatable when 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 keywords for the words people type: an ISO code, a nickname, an old name. Use group when the list has natural sections.
  • Pick the display to fit the field: chips when it is wide, collapse={2} when it is not, count in a narrow filter bar.
  • Add selectAll when picking most of the list is common, and max when the form has a real limit.
  • Keep closeOnSelect off. Closing after each pick turns three choices into three round-trips.
  • Set name to 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 collapse or display="count".
  • Turn searchable off on a list longer than about a dozen.
  • Let creatable write to the database on every keystroke. Handle oncreate and decide there what a new value means.

Quick reference

size
  • sm
  • default (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 bindable

Defaults 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 bindable

Defaults to ''

string

What's in the search box. Bindable.

variant

Defaults to 'outline'

InputVariant
size

Defaults to 'default'

InputSize
sm
default
lg
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 bindable

Defaults to null

HTMLInputElement | null