apx-dsv0.1Local renderer · live
K
Foundations4
  • Getting started
  • Theming
  • Templates
  • Icons
  • Avatar
  • Badge
  • DataGrid
  • Scheduler
  • Stat
  • Table
  • Timeline
  • TreeView
  • Accordion
  • Alert
  • EmptyState
  • Progress
  • Skeleton
  • Spinner
  • SplashScreen
  • Toast
  • ColorPicker
  • FileUpload
  • Form
  • Rating
  • TagsInput
  • Combobox
  • Field
  • Select
  • Toggle
  • Button
  • Calendar
  • Checkbox
  • DatePicker
  • Input
  • NumberInput
  • Radio
  • Slider
  • Switch
  • Textarea
  • AppShell
  • Div
  • Divider
  • Sidebar
  • Stack
  • Typography
  • Image
  • Breadcrumbs
  • Carousel
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Toolbar
  • CommandPalette
  • Confirm
  • Drawer
  • HoverCard
  • Menu
  • Modal
  • Popover
  • Tooltip
  • Icon
  • Card
  • PricingCard
60 componentsapx-ds/renderer
Select
Variant↳ other

Forms

Select

Form-control combobox — the intersection of

Overview

<Select /> is the canonical form-control combobox — the intersection of <Input />'s shell and <Menu />'s listbox. A single-select dropdown with the W3C Listbox + Combobox ARIA pattern, type-ahead, keyboard navigation, native form integration via a hidden <input>, and a trigger that matches Input / Textarea heights per size so they line up on the same form row.

Overview — country selector with realistic options

Loading preview…
Overview.tsx

When to use

  • A short list of mutually-exclusive options bound to a form field: country, plan, language.
  • A category picker that needs grouping / labels / separators between sections.
  • Anywhere a native <select> would work but the design wants custom items (icons, two-line rows, themed chrome).

When NOT to use

  • Multi-select with chips — use <MultiSelect /> when it ships.
  • Typeable filter / autocomplete — use <Combobox /> when it ships.
  • A free-form text input with suggestions — <Combobox />.
  • A non-form chooser (single-pick preference inside a Menu) — use <Menu.RadioGroup> instead.
  • The list is just actions, not values — use <Menu />.

Anatomy

text
<Select>
  <Select.Trigger />
  <Select.Content>
    <Select.Group>
      <Select.Label>      section header     </Select.Label>
      <Select.Item value="…"> option </Select.Item>
    </Select.Group>
    <Select.Separator />
    <Select.Group>
      <Select.Item value="…"> option </Select.Item>
    </Select.Group>
  </Select.Content>
</Select>
<Select>
  <Select.Trigger />
  <Select.Content>
    <Select.Group>
      <Select.Label>      section header     </Select.Label>
      <Select.Item value="…"> option </Select.Item>
    </Select.Group>
    <Select.Separator />
    <Select.Group>
      <Select.Item value="…"> option </Select.Item>
    </Select.Group>
  </Select.Content>
</Select>
  • Select — state owner + context provider. Owns value, open, the item registry, the persistent value→label cache (so the trigger renders the right label even when the listbox is closed), and the lifecycle hooks (useEscapeStack, useOutsideClick). Renders a hidden <input type="hidden" name value> when name is set so the field participates in <form> submission.
  • Select.Trigger — role="combobox" + aria-haspopup="listbox". Renders the selected label (or placeholder) plus a chevron that rotates 180° when open. Reads visual axes from the root via context — no axis props of its own.
  • Select.Content — portal-rendered, positioned, animated listbox. Defaults to matchTriggerWidth={true} (Floating UI's size middleware syncs width to the trigger). Carries its own visual axis (solid / outline / soft) independent of the trigger's form-control variant.
  • Select.Item — role="option" with aria-selected. Fires selection for both keyboard Enter/Space and mouse click. Renders a <Check> indicator on the logical end when selected.
  • Select.ItemIndicator — standalone indicator slot for custom check marks; the default <Select.Item> renders its own Check, so most consumers won't reach for this directly.
  • Select.Group — role="group" wrapper. Pair with <Select.Label> for a section header.
  • Select.Label — non-interactive section heading. role="presentation".
  • Select.Separator — role="separator" horizontal rule.

Variants

Trigger variants (form-control vocabulary)

The Trigger reuses Input / Textarea's 4 × 7 matrix verbatim.

VariantChrome
outline1px border + paper background. Default.
solidbg-subtle resting; pops to paper on focus.
ghostBorderless at rest; gains border + tint on hover/focus.
underlineBottom rule only; minimal chrome.

Content variants (overlay vocabulary)

Same 3 as Popover / Menu, defaulting to solid. Independent from the Trigger's form-control variant — mixing an underline trigger with a solid listbox is supported and reads naturally.

Sizes

Trigger heights match Input / Textarea per size — visual alignment on the same form row is a hard requirement.

SizeTrigger heightItem paddingFont
smh-8px-2 py-1text-xs
mdh-10px-2 py-1.5text-sm
lgh-12px-3 py-2text-base

Form integration

Pass name and the root renders <input type="hidden" name={name} value={value}> so the value posts with the surrounding form. required triggers native form validation when no value is set (combined with :invalid styling on the trigger via the aria-invalid bridge).

tsx
<form>
  <Select name="country" required placeholder="Pick a country">
    <Select.Trigger />
    <Select.Content>
      <Select.Item value="fr">France</Select.Item>
    </Select.Content>
  </Select>
  <button type="submit">Subscribe</button>
</form>
<form>
  <Select name="country" required placeholder="Pick a country">
    <Select.Trigger />
    <Select.Content>
      <Select.Item value="fr">France</Select.Item>
    </Select.Content>
  </Select>
  <button type="submit">Subscribe</button>
</form>

Behavior

PropDefaultEffect
closeOnEscapetrueEsc closes the listbox.
closeOnOutsideClicktruePointer-down outside trigger + content closes the listbox.
matchTriggerWidthtrue(on <Select.Content>) Listbox stretches to trigger width.
looptrueArrow-key wrap at top/bottom.
typeAheadtrueType a prefix to jump to the matching item.
placeholder—Shown in the trigger when no value is selected.

Accessibility

ARIA Listbox + Combobox pattern (W3C APG):

  • Trigger — role="combobox" + aria-haspopup="listbox" + aria-expanded + aria-controls (only when open). role="combobox" is a nameFromAuthor role: pass aria-label, aria-labelledby, or rely on the auto-fallback to placeholder for the accessible name. A surrounding <label htmlFor={triggerId}> works the same way it would for <input>.
  • Content — role="listbox" + aria-labelledby the trigger + aria-activedescendant set to the currently-highlighted item id while open (highlight, don't focus — same discipline as Menu).
  • Items — role="option" + aria-selected + aria-disabled for disabled items + data-highlighted="true" for the keyboard-highlighted row.
  • useFormFieldA11y wires id / aria-invalid / aria-required / aria-describedby from the root onto the trigger — same hook Input / Textarea use.

Keyboard:

KeyAction
Space / EnterToggle the listbox open / closed
ArrowDown / ArrowUpOpen + seed highlight to the selected (or first / last enabled) item
ArrowDown / ArrowUp (open)Cycle highlight; wraps with loop=true
Home / EndFirst / last enabled item
Enter / Space (open)Select highlighted item, close listbox
EscClose without selecting
Tab / Shift+TabClose + move focus to the next form field
Printable characterType-ahead — prefix match within ~500 ms
Same letter twiceCycle through items starting with that letter

Focus management:

  • Focus moves into Content on open (Content carries tabIndex={-1}).
  • Focus returns to the trigger on close.
  • The highlighted item auto-scrolls into view via scrollIntoView({ block: 'nearest' }) so keyboard nav past the visible window stays smooth.

axe-core: zero violations across every trigger variant and sampled colors, plus disabled / invalid / grouped states.

Examples

Basic usage — pick from a short list

Loading preview…
Basic.tsx

WithGroups

Loading preview…
WithGroups.tsx

WithIcons

Loading preview…
WithIcons.tsx

Variants

Loading preview…
Variants.tsx

Sizes

Loading preview…
Sizes.tsx

Colors

Loading preview…
Colors.tsx

Disabled

Loading preview…
Disabled.tsx

Invalid

Loading preview…
Invalid.tsx

Required

Loading preview…
Required.tsx

DisabledItems

Loading preview…
DisabledItems.tsx

LongList

Loading preview…
LongList.tsx

MatchTriggerWidth

Loading preview…
MatchTriggerWidth.tsx

PortalContainer

Loading preview…
PortalContainer.tsx

InsideField

Loading preview…
InsideField.tsx

Controlled

Loading preview…
Controlled.tsx

Theming

tsx
defineTheme({
  components: {
    Select: {
      defaultProps: { /* root behavior props — loop / typeAhead / closeOnEscape */ },
      styleOverrides: {
        trigger: 'shadow-sm',
        triggerChevron: '',
        content: 'shadow-xl',
        item: 'rounded-md',
        itemIndicator: '',
        label: '',
        group: '',
        separator: 'bg-border-strong',
      },
    },
  },
});
defineTheme({
  components: {
    Select: {
      defaultProps: { /* root behavior props — loop / typeAhead / closeOnEscape */ },
      styleOverrides: {
        trigger: 'shadow-sm',
        triggerChevron: '',
        content: 'shadow-xl',
        item: 'rounded-md',
        itemIndicator: '',
        label: '',
        group: '',
        separator: 'bg-border-strong',
      },
    },
  },
});

Per-instance overrides via <Select.Trigger className sx style /> and <Select.Content className sx style /> merge on top of the theme overrides, which merge on top of the recipe — same precedence chain everywhere else in the DS.

Props

PropTypeDefaultDescription
closeOnEscapeboolean—Default: `true`. Esc closes the listbox.
closeOnOutsideClickboolean—Default: `true`. Pointer-down outside trigger + content closes the listbox.
colorResponsiveValue<SelectColor>—Palette role — accents border / ring / bg per the form-control matrix. Default: `'neutral'`.
defaultOpenboolean—Default open state for the listbox.
defaultValuestring—Uncontrolled initial value. Default: `''`.
disabledboolean—Disables the trigger + skips opening.
fullWidthResponsiveValue<boolean>—Stretch the trigger to fill its container's width. Default: `true`.
idstring—Explicit id for the trigger; `useFormFieldA11y` generates one if omitted.
invalidboolean—Visual + a11y invalid state. Drives `aria-invalid` on the trigger + the danger border/ring.
namestring—Form name. When present, a hidden `<input type="hidden" name value>` participates in form submission.
onOpenChange(open: boolean) => void—Fires when the listbox opens / closes.
onValueChange(value: string) => void—Fires when the value changes (controlled or uncontrolled).
openboolean—Controlled open state.
placeholderstring—Placeholder text shown when no value is selected.
requiredboolean—Standard form-control flag — mirrors `<input required>`. Adds `aria-required` to the trigger.
sizeResponsiveValue<SelectSize>—Size axis — Trigger height matches Input/Textarea per size (sm=h-8, md=h-10, lg=h-12). Default: `'md'`.
valuestring—Controlled value.
variantResponsiveValue<SelectVariant>—Trigger chrome. Default: `'outline'`.