Skip to content

Select

import { Select } from '@delightstack/components';

The SelectOption type is also available:

import type { SelectOption } from '@delightstack/components';
View code
<script>
import { Select } from '@delightstack/components';
let country = $state('');
const countries = [
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'uk', label: 'United Kingdom' },
{ value: 'de', label: 'Germany' },
];
</script>
<Select bind:value={country} options={countries} label="Country" placeholder="Select a country" />
View code
<Select
options={[
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'uk', label: 'United Kingdom' },
{ value: 'de', label: 'Germany' },
]}
label="Country"
placeholder="Select a country"
/>

Enable searchable to add a filter input in the dropdown.

View code
<Select
searchable
bind:value={country}
options={countries}
label="Country"
/>

Selected values appear as chips in the trigger. Each chip has a remove button.

View code
<script>
import { Select } from '@delightstack/components';
let selectedTags = $state<string[]>([]);
const tags = [
{ value: 'featured', label: 'Featured' },
{ value: 'new', label: 'New' },
{ value: 'sale', label: 'On Sale' },
];
</script>
<Select multiple bind:value={selectedTags} options={tags} label="Tags" clearable />

Options with a group property are visually grouped under section headers.

View code
<script>
import { Select } from '@delightstack/components';
const options = [
{ value: 'apple', label: 'Apple', group: 'Fruits' },
{ value: 'banana', label: 'Banana', group: 'Fruits' },
{ value: 'carrot', label: 'Carrot', group: 'Vegetables' },
{ value: 'broccoli', label: 'Broccoli', group: 'Vegetables' },
];
</script>
<Select bind:value={selected} options={options} label="Food" />

Use onsearch to load options dynamically. The component debounces search input internally.

View code
<script>
import { Select } from '@delightstack/components';
let selected = $state('');
let options = $state([]);
let isLoading = $state(false);
</script>
<Select
searchable
loading={isLoading}
onsearch={async ({ query }) => {
isLoading = true;
options = await searchAPI(query);
isLoading = false;
}}
bind:value={selected}
{options}
label="Search users"
/>

Allow users to type new values when no match is found.

View code
<script>
import { Select } from '@delightstack/components';
let selected = $state([]);
let tags = $state([
{ value: 'featured', label: 'Featured' },
{ value: 'new', label: 'New' },
]);
</script>
<Select
creatable
searchable
multiple
bind:value={selected}
options={tags}
label="Tags"
oncreate={({ value }) => {
tags = [...tags, { value, label: value }];
}}
/>
Please select a category
View code
<Select
bind:value={category}
options={categories}
label="Category"
required
error={!category ? 'Please select a category' : ''}
/>

Use the option snippet to customize how each option appears in the dropdown.

View code
<script>
import { Select } from '@delightstack/components';
let selectedUser = $state('');
const users = [
{ value: 'alice', label: 'Alice Johnson', description: 'Engineering', initials: 'AJ' },
{ value: 'bob', label: 'Bob Smith', description: 'Design', initials: 'BS' },
{ value: 'carol', label: 'Carol Williams', description: 'Marketing', initials: 'CW' },
{ value: 'dave', label: 'Dave Brown', description: 'Sales', initials: 'DB' },
];
</script>
<Select options={users} bind:value={selectedUser} label="Assign to" placeholder="Select a team member">
{#snippet option(opt)}
<div style="display: flex; align-items: center; gap: 0.5rem;">
<div style="width: 28px; height: 28px; border-radius: 50%; background: var(--c-primary); color: white; display: flex; align-items: center; justify-content: center; font-size: 0.7rem; font-weight: 600;">
{opt.initials}
</div>
<div>
<span>{opt.label}</span>
<span style="font-size: 0.8em; opacity: 0.6;">{opt.description}</span>
</div>
</div>
{/snippet}
</Select>

The trigger is transparent (outlined) by default so it sits cleanly on any surface. Set filled to paint a solid surface background behind it.

View code
<Select label="Outlined (default)" placeholder="Transparent surface" options={countries} />
<Select label="Filled" placeholder="Solid surface" options={countries} filled />
PropTypeDefaultDescription
valueunknown-Selected value (single or array for multi), bindable
optionsSelectOption[][]Available options
multiplebooleanfalseAllow multi-select
searchablebooleanfalseEnable search/filter input
clearablebooleanfalseShow clear button on trigger
creatablebooleanfalseAllow creating new options
loadingbooleanfalseShow loading state in dropdown
disabledbooleanfalseDisable select
placeholderstring'Select...'Placeholder text
labelstring-Field label
errorstring-Error message (errors from a parent Form context display automatically)
parse(value: unknown) => unknown-Validator that throws a user-showable error message. Standalone it runs when the dropdown closes; inside a Form it is registered with the form instead
descriptionstring-Description text below the trigger (hidden while an error shows)
requiredbooleanfalseMark as required
size'0' | '1' | '2' | '3''1'Trigger and option size
skeletonbooleanfalseShow skeleton loading state
tooltipstring-Tooltip text on hover
densebooleanfalseCompact option spacing
comfortablebooleanfalseRelaxed option spacing
filledbooleanfalsePaint a solid surface background behind the trigger (vs the default transparent/outlined look)
idstringautoElement ID
namestring-Form field name. Inside a Form, registers the field; with no value prop the select becomes context-driven (reads/writes the form data — no bind:value needed)
classstring''Additional CSS classes
render_valueSnippet<[selected]>-Custom rendering of selected value in trigger
optionSnippet<[opt]>-Custom rendering of each option
EventDetailDescription
onchange{ value: unknown }Selection changed
onsearch{ query: string }Search query changed (for async loading)
oncreate{ value: string }New value created. Return false to reject
onopen-Dropdown opened
onclose-Dropdown closed
interface SelectOption {
value: unknown;
label: string;
disabled?: boolean;
description?: string;
group?: string;
}
  • Trigger: role="combobox", aria-expanded, aria-haspopup="listbox"
  • Dropdown: role="listbox"
  • Options: role="option", aria-selected
  • Multi-select: aria-multiselectable="true" on listbox
  • Search input: aria-label="Search options"
  • Full keyboard navigation: Tab, Arrow keys, Enter to select, Escape to close, Home/End
  • Focus management: focus moves to search/first option on open, returns to trigger on close
  • Virtual scrolling activates automatically for 100+ options