Manual UI Templates Select

Select

elements man ui/templates/select

Pick one value, or several, from a styled listbox that lines the selected option up over the trigger.

Example
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" }, ]} />

Parts

Part What it is
Select The root: the trigger button, the hidden form input and the listbox popup. Owns value, values and open.
SelectItem An option. value is what the select takes; the slot is its label. Shows a check while selected.
SelectGroup A labelled group of items (role=group, named by its label).
SelectLabel A label row that arrow keys skip.
SelectSeparator A line between items or groups.

Attributes

Select

Attribute Type Default Description
value unknown (two-way) "" The selected value: a string, a number, an object or null. "" or null shows the placeholder. Bind it to state; the select writes back what the user picks.
values unknown[] (two-way) [] With multiple: the selected values, in the order picked.
multiple boolean false Select several values. The popup stays open as options toggle; the trigger lists the labels.
open boolean (two-way) false Whether the listbox is open. Bind it to open and close it from code.
items SelectItems [] Options as data: { value, label?, disabled?, group? }[], a record of value to label, or groups as { value: groupLabel, items }[]. Renders the items and, on the server, the selected label.
isItemEqualToValue ((item, value) => boolean) | null null Matches the bound value to its item, for object values that are equal but not the same object. Without it, values match by key: the string, itemToStringValue, or JSON for an object.
itemToStringLabel ((item) => string) | null null The trigger label for a value.
itemToStringValue ((item) => string) | null null What a form submits for a value, and the item's data-value.
placeholder string "" Shown in the trigger while nothing is selected (data-placeholder).
label string "" aria-label for the trigger, when no <label> names it.
name string "" Form field name. The value submits with the form; with multiple, one entry per value.
form string "" The id of a form outside the select's ancestors to submit with.
autocomplete string "" Autofill hint for the hidden input (country, address-level1).
required boolean false The form does not submit while nothing is selected; the browser's message points at the trigger.
disabled boolean false Not focusable, does not open, does not submit.
readOnly boolean false Focusable and submitted, but does not open or change.
invalid boolean false aria-invalid and the danger border. A <Field> sets this for you.
position "item-aligned" | "popper" "item-aligned" item-aligned puts the selected item over the trigger's text (falls back to popper with no room, or on touch). popper opens beside the trigger like a popover.
side "top" | "right" | "bottom" | "left" "bottom" popper: the preferred side. Flips when there is no room.
align "start" | "center" | "end" "start" popper: alignment along that side.
sideOffset number 4 popper: gap from the trigger, in px.
alignOffset number 0 popper: shift along the alignment axis, in px.
avoidCollisions boolean true popper: flip and shift to stay on screen.
collisionPadding number 10 Room kept from the viewport edge, in px, in both positions.
hideWhenDetached boolean false popper: hide the popup while the trigger is scrolled out of view.
collisionAvoidance { side?: "flip" | "none", align?: "shift" | "flip" | "none", fallbackAxisSide?: "start" | "end" | "none" } {} popper: how the popup stays on screen, per axis. side none never flips, align none never shifts, align flip tries the other alignment first.
sticky boolean false popper: the popup stays shown and in view after the trigger scrolls away.
anchor string | Element | null null popper: a selector or element to place against instead of the trigger.
collisionBoundary string | Element | Element[] | null "" popper: a selector or elements whose edges flip and shift keep the popup inside, besides the viewport.
disableAnchorTracking boolean false popper: placed when it opens and again on a scroll or resize, not when the trigger moves or the popup resizes on its own.
arrow boolean false popper: an arrow pointing at the trigger.
arrowPadding number 5 popper: room the arrow keeps from the popup's corners, in px.
backdrop boolean false An overlay under the popup; a press on it closes.
modal boolean true While open, the page does not scroll and presses outside only close the select.
loop boolean false Arrow keys wrap from the last option to the first.
highlightItemOnHover boolean true Moving the mouse over an option highlights it. false leaves the highlight to the keyboard.
scrollButtons boolean true Arrows at the top and bottom of a long list; hovering one scrolls.
size "sm" | "md" | "lg" "md" Trigger height from the control tokens.
block boolean false Full width.
dir "ltr" | "rtl" | "" "" Text direction; empty inherits.
class string "" Extra classes on the trigger.
contentClass string "" Extra classes on the popup.
attrs Record<string, string | number | boolean | undefined> {} Spread onto the trigger: id (for <label for>), aria-*, data-testid.
onValueChange (value: string, details: ChangeDetails) => void () => {} Called when the user picks a different value, with reason item-press (none for typeahead on the closed trigger). It runs before the change with details second (reason, event, trigger, cancel(), isCanceled); details.cancel() keeps the old value.
onValuesChange (values: string[], details: ChangeDetails) => void () => {} multiple: called when the user toggles an option, with reason item-press. It runs before the change with details second (reason, event, trigger, cancel(), isCanceled); details.cancel() keeps the old values.
onOpenChange (open: boolean, details: ChangeDetails) => void () => {} Called when the select opens or closes itself, with a reason: trigger-press, list-navigation (an arrow, Home or End key), item-press, outside-press, escape-key, focus-out (Tab, or focus leaving) or window-resize. It runs before the change with details second ( reason, event, trigger, cancel(), isCanceled); details.cancel() keeps the popup as it is.
onOpenChangeComplete (open: boolean) => void () => {} Called after the open or close animation.
onEscapeKeyDown (e: KeyboardEvent) => void () => {} Escape while open; e.preventDefault() keeps it open.
onPointerDownOutside (e: PointerEvent) => void () => {} A press outside while open; e.preventDefault() keeps it open.
onCloseAutoFocus (e: Event) => void () => {} Before focus returns to the trigger on close; e.preventDefault() leaves it.

SelectItem

Attribute Type Default Description
value unknown (required) Required. The value the select takes when this item is chosen: a string, or any value the root's isItemEqualToValue compares.
disabled boolean false Skipped by the keyboard, cannot be chosen.
textValue string "" Text for typeahead when the label is not plain text.
label string "" Text the trigger shows when this item is selected, instead of the item's text.

SelectGroup

Attribute Type Default Description
label string "" The group's label, which also names the group.

SelectItem, SelectGroup, SelectLabel, SelectSeparator

Attribute Type Default Description
class string "" Extra classes.

Slots

Slot Part Content
default SelectItem, SelectGroup, SelectLabel and SelectSeparator parts, after any items.
display Custom trigger content instead of the selected label (bind value to keep it in step).
icon Replaces the chevron in the trigger.
default SelectItem The item's label: text, icons, anything.
indicator SelectItem Replaces the check shown while the item is selected.

Keyboard

Keys Action
Space / Enter On the closed trigger: opens with the selected option (or the first) highlighted. While open: chooses the highlighted option.
ArrowDown / ArrowUp Closed: opens. Open: moves the highlight; disabled options are skipped.
Home / End Opens, or moves, to the first or last option.
PageUp / PageDown Moves the highlight ten options.
A to Z Closed: selects the next option starting with the typed text. Open: highlights it.
Alt + ArrowUp Chooses the highlighted option and closes.
Escape Closes without choosing; focus stays on the trigger.
Tab Closes and moves on.

Data attributes

Attribute Values On Description
data-state "open" | "closed" .select-trigger, .select-content
data-placeholder present .select-trigger Nothing is selected.
data-disabled present .select-trigger, .select-item
data-readonly present .select-trigger
data-required present .select-trigger
data-popup-open present .select-trigger The popup is open.
data-popup-side "none" | "top" | "right" | "bottom" | "left" .select-trigger The open popup's data-side.
data-pressed, data-focused present .select-trigger During a press; while focused.
data-filled, data-dirty, data-touched present .select-trigger Has a value; the value changed from the first one; focus has left once. A form reset clears dirty and touched.
data-invalid, data-valid present .select-trigger aria-invalid is true; the enclosing Field is valid.
data-highlighted present .select-item The keyboard or mouse highlight.
data-selected present .select-item
data-state "checked" | "unchecked" .select-item
data-position "item-aligned" | "popper" .select-content How it was placed this time.
data-side "none" | "top" | "right" | "bottom" | "left" .select-content none when item-aligned.
data-anchor-hidden present .select-content Popper with hideWhenDetached: while the trigger is scrolled out of view.
data-side, data-align side, alignment .select-arrow Where the arrow sits, after a flip.
data-uncentered present .select-arrow The arrow cannot point at the trigger's middle.
data-state "open" | "closed" .select-arrow, .select-backdrop
data-visible present .select-scroll The list can scroll that way.
data-direction "up" | "down" .select-scroll
data-side "top" | "bottom" | "left" | "right" | "none" .select-scroll The popup's side, none while item-aligned.
data-placeholder present .select-value No value is chosen, so the placeholder shows.
data-popup-open present .select-icon The popup is open.
data-align "start" | "center" | "end" .select-content
--anchor-height, --available-width px .select-content The trigger's height; room across.
--anchor-width px .select-content The trigger's width; the popup is at least this wide.
--available-height px .select-content Room to the viewport edge; the list scrolls inside it.
--transform-origin position .select-content The trigger's text when item-aligned, else the anchor edge.

Accessibility

  • The trigger is the APG select-only combobox: role="combobox", aria-haspopup="listbox", aria-expanded, aria-controls the listbox.
  • Focus stays on the trigger. aria-activedescendant names the highlighted option; options carry aria-selected.
  • The listbox is named by whatever names the trigger: a <label for>, aria-labelledby, or label.
  • Groups are role="group" named by their label; labels and separators are skipped by the keyboard.
  • A <label for> click focuses the trigger without opening it.
  • required and invalid set aria-required and aria-invalid on the trigger; the browser's required message points at it.

See also

  • Combobox (opens in a new tab): A text input that filters a list of options as you type, for one value or several shown as chips, with async results.
  • Command (opens in a new tab): A searchable, ranked list of actions run from the keyboard: inline, in a popover, or as a command palette.