Select
Pick one value, or several, from a styled listbox that lines the selected option up over the trigger.
Basic
Click, or focus and press Space, Enter or an arrow. The selected option opens over the trigger; type to jump.
Code
<Select value={form.fruit} placeholder="Select a fruit" label="Fruit">
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
<SelectItem value="blueberry">Blueberry</SelectItem>
<SelectItem value="grapes">Grapes</SelectItem>
<SelectItem value="pineapple">Pineapple</SelectItem>
</Select>Usage
import { Select, SelectItem, SelectGroup, SelectLabel, SelectSeparator } from "@elements/ui/select";
<Select value={form.fruit} placeholder="Select a fruit" name="fruit" label="Fruit">
<SelectGroup label="Fruits">
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
<SelectItem value="blueberry">Blueberry</SelectItem>
</SelectGroup>
</Select>
<!-- or from data -->
<Select value={form.theme} items={[
{ value: "light", label: "Light" },
{ value: "dark", label: "Dark" },
{ value: "system", label: "System" },
]} />Examples
Clearable
An option with an empty value clears the selection, and the trigger shows the placeholder again.
Code
<Select value={form.fruit} placeholder="Select a fruit" label="Fruit">
<SelectItem value="">None</SelectItem>
<SelectItem value="apple">Apple</SelectItem>
...
</Select>Items
items renders the options from data, and the selected label from the server's first response.
Code
const THEMES = [
{ value: "light", label: "Light" },
{ value: "dark", label: "Dark" },
{ value: "system", label: "System" },
];
<Select value={prefs.theme} items={THEMES} placeholder="Theme" label="Theme" />Groups
SelectGroup labels a set of options; SelectSeparator draws a line. Labels, separators and disabled options are skipped.
Code
<Select value={form.food} placeholder="Select a food" label="Food">
<SelectGroup label="Fruits">
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
</SelectGroup>
<SelectSeparator />
<SelectGroup label="Vegetables">
<SelectItem value="carrot">Carrot</SelectItem>
<SelectItem value="leek" disabled>Leek</SelectItem>
</SelectGroup>
</Select>Scrollable
A long list scrolls inside the room it has. The arrows at the ends scroll while hovered.
Code
<Select value={form.zone} placeholder="Select a timezone" label="Timezone">
<SelectGroup e:for={zone of ZONES} label={zone.group}>
<SelectItem e:for={[value, label] of zone.items} {value}>{label}</SelectItem>
</SelectGroup>
</Select>Position
item-aligned (the default) puts the selected option over the trigger. popper opens beside it, with side, align, offsets and an arrow.
Code
<Select position="item-aligned" ...> <!-- default: the selected item sits over the trigger -->
<Select position="popper" ...> <!-- below the trigger, like a popover -->
<Select position="popper" side="top" align="end" sideOffset={8} alignOffset={6} arrow ...>Positioner
arrowPadding keeps the arrow off the popup's corners. collisionAvoidance chooses whether the popup flips or shifts. sticky keeps it in view after the trigger scrolls away. disableAnchorTracking places it when it opens and on a scroll or resize, not when the page shifts under it. backdrop puts an overlay under the popup.
Code
<Select position="popper" arrow arrowPadding={12} alignOffset={160} ...> <!-- the arrow keeps 12px from the corner -->
<Select position="popper" collisionAvoidance={{ side: "none" }} ...> <!-- never flips; shrinks instead -->
<Select position="popper" sticky ...> <!-- stays in view after the trigger scrolls away -->
<Select position="popper" disableAnchorTracking ...> <!-- placed on open, on scroll and on resize only -->
<Select backdrop ...> <!-- an overlay under the popup -->Anchor and boundary
With position popper, anchor places the popup against another element than the trigger, and collisionBoundary keeps it inside an element's edges as well as the viewport.
Code
<span id="fruit-anchor">Anchor</span>
<Select position="popper" anchor="#fruit-anchor" ...> <!-- placed against another element -->
<div class="box">
<Select position="popper" collisionBoundary=".box" ...> <!-- flips and shrinks to stay in the box -->
</div>Disabled
A disabled select cannot be focused or opened. A disabled option is shown but skipped.
Code
<Select disabled value="apple" items={FRUITS} label="Fruit" />
<Select value={form.x} label="Size">
<SelectItem value="a">Available</SelectItem>
<SelectItem value="b" disabled>Sold out</SelectItem>
</Select>Invalid
invalid sets aria-invalid and the danger border. In a Field, the label names the select and the error sits on its reserved line.
Code
<Field label="Fruit" error={form.fruit ? "" : "Please select a fruit."}>
<Select value={form.fruit} placeholder="Select a fruit" invalid={!form.fruit} items={FRUITS} />
</Field>Form
name submits the value with the form; required blocks an empty submit; reset restores the first value.
Code
<form onsubmit={(e) => save(e)}>
<Select name="fruit" required value={form.fruit} items={FRUITS} placeholder="Fruit" label="Fruit" />
<Select name="country" value={form.country} items={COUNTRIES} autocomplete="country" label="Country" />
<Button type="submit" variant="primary">Submit</Button>
<Button type="reset">Reset</Button>
</form>Controlled
Bind value and open. onValueChange and onOpenChange report only what the select did itself.
Code
<Select value={state.fruit} open={state.open} label="Fruit"
onValueChange={(v) => log(v)} onOpenChange={(o) => log(o)}>...</Select>
<Button onclick={() => state.open = true}>Open</Button>
<Button onclick={() => state.fruit = "banana"}>Set banana</Button>Multiple
multiple toggles options and stays open; values holds them in the order picked, and each submits under name.
Code
<Select multiple values={form.toppings} placeholder="Toppings" name="toppings" label="Toppings">
<SelectItem value="cheese">Cheese</SelectItem>
<SelectItem value="mushrooms">Mushrooms</SelectItem>
...
</Select>Multiple with a count
The display slot shows the first chosen label and how many more are chosen, so a fixed-width trigger never cuts the choice off. position="popper" opens the list below the trigger.
Code
<Select multiple values={form.languages} name="languages" position="popper" label="Languages" class="wide">
<span slot="display">{languagesLabel(form.languages)}</span>
<SelectItem e:for={l of LANGUAGES} value={l.value}>{l.label}</SelectItem>
</Select>
function languagesLabel(values: string[]): string {
if (values.length === 0) {
return "Select languages";
}
let first = LANGUAGES.find((l) => l.value === values[0])?.label ?? values[0];
return values.length === 1 ? first : `${first} (+${values.length - 1} more)`;
}Sizes
size follows the control height tokens.
Code
<Select size="sm" ...>
<Select ...>
<Select size="lg" ...>Custom items
Items take icons and any markup; textValue keeps typeahead working, and the indicator slot replaces the check.
Code
<Select value={task.status} label="Status">
<SelectItem value="todo" textValue="Todo">
<span class="dot is-todo"></span> Todo
</SelectItem>
<SelectItem value="done" textValue="Done">
<Icon name="check-circle" /> Done
<Icon slot="indicator" name="dot" />
</SelectItem>
</Select>Custom trigger content
The display slot renders what the trigger shows, such as the selected person's avatar; label names the trigger. The icon slot replaces the chevron.
Code
<Select value={task.owner} label="Assignee" class="wide">
<span slot="display" class="person">
<span class="avatar">{initials(owner(task.owner)?.name ?? "")}</span>
{owner(task.owner)?.name ?? "Assign to..."}
</span>
<Icon slot="icon" name="chevrons-up-down" />
<SelectItem e:for={p of PEOPLE} value={p.value} textValue={p.name}>
<span class="avatar">{initials(p.name)}</span> {p.name}
</SelectItem>
</Select>Hide when detached
In a scrolling box, hideWhenDetached hides the popup while its trigger is scrolled out of view, and shows it again when it returns.
Code
<div class="scroll-box">
<Select position="popper" modal={false} hideWhenDetached items={FRUITS} label="Fruit" />
</div>Read only
readOnly keeps the select focusable and submitted, but it does not open or change.
Code
<Select readOnly value="pst" items={ZONES} label="Timezone" />Options
loop wraps the arrows; highlightItemOnHover={false} leaves the highlight to the keyboard; onEscapeKeyDown and onPointerDownOutside can keep it open; onCloseAutoFocus can keep focus from returning; open={true} with no binding opens it when it mounts.
Code
<Select loop ...> <!-- arrows wrap -->
<Select highlightItemOnHover={false} ...> <!-- the mouse does not move the highlight -->
<Select onEscapeKeyDown={(e) => e.preventDefault()} ...>
<Select onPointerDownOutside={(e) => e.preventDefault()} <!-- an outside press keeps it open -->
onCloseAutoFocus={(e) => e.preventDefault()} ...> <!-- focus is not returned on close -->
<Select open={true} ...> <!-- unbound: open when it mounts (defaultOpen) -->RTL
In a right-to-left region the popup lines up from the right and the check sits on the left.
Code
<Select dir="rtl" value={form.fruit} placeholder="اختر فاكهة" label="فاكهة">
<SelectItem value="apple">تفاحة</SelectItem>
...
</Select>Change details
onValueChange and onOpenChange get event details as a second argument: the reason (trigger-press, item-press, escape-key, outside-press), the event and cancel().
Code
<Select value={form.fruit} items={fruits} label="Fruit"
onValueChange={(value, details) => log(value, details.reason)}
onOpenChange={(open, details) => log(open, details.reason)} />
<!-- details.cancel() keeps the current state -->
<Select items={fruits} label="Fruit" onValueChange={(value, details) => {
if (value === "banana") details.cancel();
}} />Object values
A value can be an object. isItemEqualToValue matches the bound value to its item, itemToStringLabel shows it and itemToStringValue submits it. items also takes a record of value to label, or groups.
Code
<Select value={form.user}
items={USERS.map((user) => ({ value: user }))}
isItemEqualToValue={(item, value) => item.id === value.id}
itemToStringLabel={(user) => user.name}
itemToStringValue={(user) => String(user.id)}
name="user" label="User" />
<Select items={{ apple: "Apple", banana: "Banana" }} label="Fruit" /> <!-- a record of value to label -->
<Select items={[{ value: "Fruits", items: [...] }, ...]} label="Food" /> <!-- groups -->Inside a dialog
A select in a modal dialog: Escape and an outside press close the select first and leave the dialog open; a second Escape closes the dialog.
Code
<Dialog title="Edit fruit">
<Button slot="trigger">Edit fruit</Button>
<Select value={form.fruit} placeholder="Select a fruit" label="Fruit">
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
</Select>
</Dialog>