Select
elements man ui/templates/selectPick one value, or several, from a styled listbox that lines the selected option up over the trigger.
Exampleimport { 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.