# Select
Pick one value, or several, from a styled listbox that lines the selected option up over the trigger.
```ehtml Example
import { Select, SelectItem, SelectGroup, SelectLabel, SelectSeparator } from "@elements/ui/select";
Apple
Banana
Blueberry
```
## 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 `` 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 `` 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` | `{}` | Spread onto the trigger: id (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 ``, aria-labelledby, or label.
- Groups are role="group" named by their label; labels and separators are skipped by the keyboard.
- A `` 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](combobox): A text input that filters a list of options as you type, for one value or several shown as chips, with async results.
- [Command](command): A searchable, ranked list of actions run from the keyboard: inline, in a popover, or as a command palette.