Select
betav0.1.0Single-value dropdown selector for choosing one option from a predefined list. Compound-component API built on @radix-ui/react-select with integrated label, helper text, and error state. Multi-value selection is permanently out of scope (arbitration Q3) — see the dedicated MultiSelect component (ROADMAP, not yet built).
Variants
Choose a time range
Examples
<Select value={value} onValueChange={setValue}>
<SelectLabel>Relative time</SelectLabel>
<SelectTrigger>
<SelectValue placeholder="Select an option..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="days-1">Last 24 hours</SelectItem>
<SelectItem value="days-7">Last 7 days</SelectItem>
<SelectItem value="months-1">Last month</SelectItem>
</SelectContent>
<SelectHelperText>Choose a time range</SelectHelperText>
</Select>// clearable — the × appears only once a value is set, and never while disabled
// (RULE-12, same contract as Combobox's own `clearable`)
<Select clearable value={value} onValueChange={setValue}>
<SelectLabel>Relative time</SelectLabel>
<SelectTrigger>
<SelectValue placeholder="Select an option..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="days-1">Last 24 hours</SelectItem>
<SelectItem value="days-7">Last 7 days</SelectItem>
</SelectContent>
</Select>// error lives on the Root only (arbitration Q1) — SelectTrigger and
// SelectHelperText need no prop, both read it from context
<Select value={value} onValueChange={setValue} error>
<SelectLabel required>Relative time</SelectLabel>
<SelectTrigger>
<SelectValue placeholder="Select an option..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="days-1">Last 24 hours</SelectItem>
</SelectContent>
<SelectHelperText>This field is required</SelectHelperText>
</Select>// SelectGroup + SelectSeparator for categorized options (RULE-08: never
// nest a Select inside another Select's content — use groups instead)
<Select value={value} onValueChange={setValue}>
<SelectLabel>Entity type</SelectLabel>
<SelectTrigger>
<SelectValue placeholder="Select a type..." />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="report">Report</SelectItem>
<SelectItem value="malware">Malware</SelectItem>
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectItem value="identity">Identity</SelectItem>
<SelectItem value="location">Location</SelectItem>
</SelectGroup>
</SelectContent>
</Select>// No SelectLabel — an accessible name must be supplied directly, or FDS
// warns in development (never in production, never blocks rendering)
<Select value={value} onValueChange={setValue}>
<SelectTrigger aria-label="Filter by status">
<SelectValue placeholder="All statuses" />
</SelectTrigger>
<SelectContent>
<SelectItem value="open">Open</SelectItem>
<SelectItem value="closed">Closed</SelectItem>
</SelectContent>
</Select>Props
| Prop | Type |
|---|---|
Select.value | string — Controlled selected value |
Select.defaultValue | string — Uncontrolled initial value |
Select.onValueChange | (value: string) => void — Fires when the selected value changes |
Select.open | boolean — Controlled open state of the dropdown |
Select.defaultOpen | boolean — Uncontrolled initial open state (default: false) |
Select.onOpenChange | (open: boolean) => void — Fires when the dropdown opens or closes |
Select.disabled | boolean — Disables the trigger and prevents the dropdown from opening |
Select.required | boolean — Marks the underlying native form control as required (HTML5 form semantics only). Independent from SelectLabel.required, which separately controls the `*` indicator — set both explicitly if a field should show the asterisk. |
Select.name | string — Name attribute used for native form submission |
Select.error | boolean — Single source of truth for the field's error state (arbitration Q1). Propagates via context to SelectTrigger (error border) and SelectHelperText (error text color) — not a prop on any sub-part. |
Select.clearable | boolean — Adds a clear control to the trigger that empties the selection back to the placeholder (RULE-12). Same prop name and same self-hiding behaviour as Combobox's `clearable`: rendered only when there is something to clear, never while disabled. Unlike ComboboxClear it IS a tab stop, because a Select trigger offers no keyboard path to the same action. |
Select.clearLabel | string — Accessible name of the clear control (default: "Clear", iso-Combobox). Pass a field-specific name when several clearable fields share a screen. |
SelectTrigger.children | ReactNode — Typically a single SelectValue. Dev-only warning if the field has no SelectLabel and no aria-label/aria-labelledby (arbitration Q5) — advisory only, never blocks rendering. |
SelectValue.placeholder | ReactNode — Shown when no value is selected (Radix Value prop) |
SelectContent.position | "popper" | "item-aligned" — Positioning strategy (default: "popper") |
SelectContent.side | "top" | "bottom" — Preferred side when using popper positioning |
SelectContent.align | "start" | "center" | "end" — Alignment when using popper positioning |
SelectItem.value | string (required) — Value submitted/reported when this item is selected |
SelectItem.disabled | boolean — Disables this individual item (still visible, not selectable) |
SelectLabel.children | ReactNode — Label text. Optional (arbitration Q5) — never mandatory. |
SelectLabel.required | boolean — Shows an inline "*" indicator next to the label text |
SelectLabel.hasInfoIcon | boolean — Shows an info icon next to the label text |
SelectHelperText.children | ReactNode — Helper or error text. Same slot switches color/content based on Select's `error` state (arbitration Q1) — no separate `error` prop here. |
SelectGroup.children | ReactNode — One or more SelectItem, grouped together (RULE-08) |
SelectSeparator (no props) | Visual divider between SelectGroups — renders a single hairline, no configurable props beyond standard HTML div attributes |
Accessibility
Contrast: the disabled text on the disabled field fill (--text-input-disabled on --bg-input-disabled) is permanently exempt — WCAG 1.4.3 Note 1 excludes inactive user interface components — so no contrast pair is declared for it, deliberately and not by omission. Measured 4.35:1 dark / 3.69:1 light since the 2026-08-20 export moved both tokens onto the gray ramp. Same non-declaration as Menu's disabled row, which renders the identical two classes. RFC §5.4. item-text-hover/-focus/-selected currently share identical token pairs (Radix's Item primitive sets the same data-highlighted attribute for real mouse hover and keyboard arrow-navigation — no distinction is possible — and Figma's own 'Selected' state is documented as visually identical to 'Focus'; RFC §9 Follow-up #7), declared as distinct ids anyway per instruction to cover each state explicitly rather than assume they'll always stay merged. border-elevation-subtle (SelectSeparator) is a decorative structural divider, not a functional UI component boundary — exempt from WCAG 1.4.11 per SC 1.4.11 Note 1 (same treatment Tabs.meta.ts already documents for its own TabsList bottom border). 1.1.1 Non-text Content — chevron and info icons are aria-hidden decorative accents; the field's accessible name/state never depends on them alone. 1.3.1 Info and Relationships — Radix provides role=combobox/listbox/option and aria-selected/aria-expanded automatically; aria-labelledby (SelectLabel) and aria-describedby (SelectHelperText) are wired by FDS via context, not Radix (RFC §9 Follow-up #1 — Radix does not auto-associate an external label the way it does for a Label nested inside a Group). 1.4.1 Use of Color — error state is conveyed by border color AND helper-text content change (RULE-03/04), never color alone. 2.1.1 Keyboard — full Radix keyboard support: Space/Enter/ArrowDown opens, Arrow keys navigate items, typeahead by first character, Escape closes and returns focus to the trigger. 2.4.6 / 4.1.2 — SelectLabel is optional (arbitration Q5), never made mandatory; a dev-only console.warn (silent in production) fires when SelectTrigger has no SelectLabel and no aria-label/aria-labelledby, so a consumer always gets a signal without the API forcing a label to exist. Multi-value selection is permanently out of scope (arbitration Q3) — see the dedicated MultiSelect component (ROADMAP, not yet built); consumers needing `multiple` today keep their existing MUI/wrapper implementation until it ships (RFC §7 transitional facade).