Skip to main content

Select

The custom select widget with the field styling. Convenience mode takes options and builds the button and panel; compound mode takes Select.Label, Select.Button, Select.Options and Select.Option.

import Select from '@zuilib/primitives/select'

Options

Each option has value, label and optional icon, description, disabled. The selected option's icon is shown in the button.

Loading example

Sizes

sm, md and lg size the button from --control-height-* and the rows and icons to match. Parts inherit the size from the root.

Loading example

Multiple and object values

multiple makes value / onValueChange arrays and joins the selected labels with ", ". compareBy compares object values by a key or a predicate.

Loading example

In a Field

Inside a Field the button takes the field's Label and FieldError through aria-labelledby / aria-describedby, and invalid / disabled are inherited when unset.

Loading example

Compound parts

Children replace the auto-built button and panel. Select.Option takes icon, description and a render-prop child receiving {focus, selected, disabled}; the check mark is still drawn by the part.

Loading example

Props

SelectProps<T>:

PropTypeDefaultDescription
valuerequiredTAn array when multiple
onValueChangerequired(value: T) => void
optionsArray<SelectOptionData<T>>Each {value, label, disabled?, icon?, description?}. Auto-builds the button and panel; ignored when children are given
placeholderstring'Select option'
multiplebooleanfalsevalue / onValueChange become arrays; selected labels are joined with ", "
compareBykeyof T | (a, b) => booleanCompare object values by a key or a predicate; default is reference equality
invalidbooleanDefaults to the enclosing Field invalid
disabledbooleanDefaults to the enclosing Field / Fieldset disabled
namestringRenders hidden inputs so the value submits with a native form
formstringThe form the hidden inputs belong to
size'sm' | 'md' | 'lg''md'
fullWidthbooleantruefalse renders the root inline-block so it shrinks to the button
anchorSelectAnchor | false'bottom start'false renders the panel inline, no portal
portalbooleantrue when anchoredonly honoured with anchor={false}; every anchored panel is portalled. Use anchor={false} to keep the panel in the subtree so token overrides and .dark reach it
renderOption(option, {focus, selected, disabled}) => ReactNodeCustom option body; the check mark is still drawn
classNamestringMerged last onto the root
childrenReactNodeCompound mode

Parts

Parts inherit size, invalid and disabled from the root.

PropTypeDefaultDescription
Select.LabelpassiveLabel?: booleanpassiveLabel does not focus the button on click. A <label for> inside a Field, a <div> wired by aria-labelledby elsewhere
Select.Buttonindicator?: ReactNode | false, autoFocus?: booleanindicator replaces the chevron; false removes it. Plus button attributes
Select.Optionsanchor?, portal?, modal?, keepMounted?anchor 'bottom start'keepMounted keeps the panel mounted while closed
Select.Optionvalue, disabled?, icon?, description?, childrenchildren may be (state) => ReactNode

Slots

SlotElementNotes
[data-slot="select"]divRoot. Also data-size
[data-slot="select-label"]label | div
[data-slot="select-button"]buttondata-open / data-hover / data-focus / data-disabled
[data-slot="select-value"]spanSelected labels or placeholder (convenience mode)
[data-slot="select-value-icon"]spanThe selected option icon in the button
[data-slot="select-chevron"]svg
[data-slot="select-options"]divThe panel; portalled to <body> when anchored
[data-slot="select-option"]divdata-focus / data-selected / data-disabled
[data-slot="select-option-icon"]span
[data-slot="select-option-content"]spanLabel and description column
[data-slot="select-option-description"]span
[data-slot="select-option-check"]svgVisible only when selected

Tokens

TokenUsed for
--control-height-sm/md/lg, --control-padding-x-*Button box and row insets
--input, --background, --foregroundButton field frame
--dangerInvalid border and ring
--ringFocus ring on the button
--popover, --popover-foreground, --border, --shadow-2Panel surface
--accent, --accent-foregroundFocused row
--primaryCheck mark
--muted-foregroundPlaceholder, chevron, descriptions

Accessibility

  • The button has aria-haspopup="listbox" and aria-expanded; the panel is role="listbox" with role="option" rows and aria-selected; aria-multiselectable when multiple.
  • Keyboard: Enter, Space, ArrowUp / ArrowDown open the panel; arrows move focus; Home / End jump; typing does typeahead; Enter / Space select; Escape closes and returns focus to the button.
  • invalid sets aria-invalid on the button and the danger border. disabled sets data-disabled and removes the button from interaction.
  • Inside a Field the button is named by Label and described by FieldDescription / FieldError. In compound mode outside a field, use Select.Label (an sr-only one is fine).
  • name renders hidden inputs so native form submission and FormData see the value.
  • NativeSelect: the native <select> when the browser picker is enough.
  • Combobox: the same panel with a text input for filtering.
  • Field: label, description and error wiring.
  • Theming: the anchored panel is portalled to <body>; pass anchor={false} to keep it in the subtree so overrides reach it.