Select
A single-select listbox.
Anatomy
<script>
import { Select } from '@shardsui/svelte/select'
</script>
<Select.Root>
<Select.Label />
<Select.Trigger>
<Select.Value />
<Select.Icon />
</Select.Trigger>
<Select.Portal>
<Select.Backdrop />
<Select.Positioner>
<Select.Popup>
<Select.ScrollUpArrow />
<Select.Arrow />
<Select.List>
<Select.Item>
<Select.ItemIndicator />
</Select.Item>
<Select.Separator />
<Select.Group>
<Select.GroupLabel />
</Select.Group>
</Select.List>
<Select.ScrollDownArrow />
</Select.Popup>
</Select.Positioner>
</Select.Portal>
</Select.Root>Usage guidelines
- Prefer Combobox for large lists: Select has no filtering beyond typeahead (typing jumps to the matching item). Once the list grows long enough to need filtering, switch to Combobox.
- Positioning: the popup anchors to the trigger through
<Select.Positioner>— setside,alignand the offsets there, and size the popup against the anchor CSS variables it publishes (see Styling). - Give the control an accessible name: add a
<Select.Label>, or set anaria-labelon<Select.Trigger>when there's no visible label. See the forms guide.
TypeScript
<Select.Root> infers its item type from the value prop, so every <Select.Item>'s value (and each entry in the items array) must share that type. Adding multiple flips that type to an array.
See the TypeScript guide for generic roots, typed wrappers, and bind:ref patterns.
Examples
Formatting the value
With no items, <Select.Value> stringifies the selected value. Give <Select.Root> an items prop — an array of { value, label } entries, groups of those, or a record mapping value to label — and it renders the matching label instead:
<script lang="ts">
const items = [
{ value: null, label: 'Select theme' },
{ value: 'system', label: 'System default' },
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' }
]
</script>
<Select.Root {items}>
<Select.Value />
</Select.Root>For richer output, pass a children snippet to <Select.Value> and format the value yourself:
<script lang="ts">
const items = {
monospace: 'Monospace',
serif: 'Serif',
'sans-serif': 'Sans-serif'
}
</script>
<Select.Value>
{#snippet children(value)}
<span style="font-family: {value as string}">
{items[value as keyof typeof items]}
</span>
{/snippet}
</Select.Value>You can skip the lookup entirely by giving each item an object value.
Labeling a select
Add a visible label for the trigger with <Select.Label>:
<Select.Root>
<Select.Label>Theme</Select.Label>
<!-- ... -->
</Select.Root>Clicking the label moves focus to the trigger without opening the popup.
Placeholder values
Show prompt text before anything is chosen with the placeholder prop on <Select.Value>:
<script lang="ts">
const items = [
{ value: 'system', label: 'System default' },
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' }
]
</script>
<Select.Root {items}>
<Select.Value placeholder="Select theme" />
</Select.Root>A placeholder alone gives no way back to the empty state from the select itself. To make it clearable from the popup rather than a separate reset button, add a null item to the list:
<script lang="ts">
const items = [
{ value: null, label: 'Select theme' },
{ value: 'system', label: 'System default' },
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' }
]
</script>
<Select.Root {items}>
<Select.Value />
</Select.Root>An entry that labels null doubles as the empty-state text, so <Select.Value> renders it in place of any placeholder.
Multiple selection
Set the multiple prop on <Select.Root> and the value becomes an array of every chosen item. Render that array however you like through the <Select.Value> children snippet.
Object values
Item values can be objects, not just primitives. The <Select.Value> children snippet then receives the full object, so you can format the display from any of its fields without an items lookup. Pass isItemEqualToValue so the select matches the selected object against the list by a stable field like id.
Grouped
Break a long list into labeled sections with <Select.Group> and a <Select.GroupLabel> for each heading. Model the data as an array of group objects, each with its own items array plus a field such as label for the heading text, and render one <Select.Group> per entry.
Scrolling a long list
The scrolling container is <Select.List>, or <Select.Popup> when no list is rendered. Cap it with --available-height so it never outgrows the viewport:
.select-list {
max-height: var(--available-height);
overflow-y: auto;
}<Select.ScrollUpArrow> and <Select.ScrollDownArrow> become visible when there is more to scroll in that direction, and scroll the list while the pointer rests on them. Both render with position: absolute, so give the popup a positioning context and place them against its edges:
<Select.Popup class="select-popup">
<Select.ScrollUpArrow class="select-scroll-arrow-up" />
<Select.List class="select-list">
<!-- ... -->
</Select.List>
<Select.ScrollDownArrow class="select-scroll-arrow-down" />
</Select.Popup>Mounting either arrow hides the list's scrollbar. Neither arrow becomes visible when the popup was opened by touch, where the scrollbar stays.
API reference
Root
Groups all parts of the select.
Doesn't render its own HTML element, but renders a hidden <input> beside.
Label
An accessible label that is automatically associated with the select trigger.
Renders a <div> element.
Trigger
A button that opens the select popup.
Renders a <button> element.
Typing while it is focused and closed selects the first item whose label matches, unless multiple is set.
Value
A text label of the currently selected item.
Renders a <span> element.
Icon
An icon that indicates that the trigger button opens a select popup.
Renders a <span> element, containing a ▼ glyph when given no children.
Backdrop
An overlay displayed beneath the popup.
Renders a <div> element.
Portal
A portal that moves the popup out to <body>, clear of ancestor clipping and stacking.
Renders a <div> element.
The portal renders while the popup is mounted, and stays rendered from the first time the trigger is focused, so the items exist for closed-trigger typeahead. keepMounted keeps it rendered beyond that.
Positioner
Positions the select popup.
Renders a <div> element.
Popup
A container for the select list.
Renders a <div> element.
It carries the listbox role itself when no <Select.List> is rendered.
List
The listbox and the element that scrolls the items. Optional — without it the popup takes both roles.
Renders a <div> element.
Arrow
Displays an element positioned against the anchor.
Renders a <div> element.
Item
An individual item in the select popup.
Renders a <div> element.
ItemIndicator
Indicates whether the select item is selected.
Renders a <span> element, containing a ✔️ glyph when given no children.
Group
Groups related select items with the corresponding label.
Renders a <div> element.
GroupLabel
An accessible label that is automatically associated with its parent group.
Renders a <div> element.
ScrollUpArrow
An element that scrolls the list up while hovered. Never visible when the popup was opened by touch.
Renders an absolutely positioned <div> element, containing a ▲ glyph when given no children.
ScrollDownArrow
An element that scrolls the list down while hovered. Never visible when the popup was opened by touch.
Renders an absolutely positioned <div> element, containing a ▼ glyph when given no children.
Separator
A visual divider between groups of items. Rendered as role="presentation", because
role="separator" is not valid inside a listbox.
Renders a <div> element.