Select
Displays a list of options for the user to pick from—triggered by a button.
Made by shadcnInstallation
When to use
Select components provide single-option selection from a list of options. Use this decision tree:
Usage
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>Anatomy
The Select component is built with Base UI primitives:
<Select> (Root Container)
├── <SelectTrigger> (Button)
│ ├── <SelectValue> (Placeholder/Selected)
│ └── <ChevronDown> Icon
├── <SelectContent> (Dropdown)
│ ├── Portal rendering
│ ├── Positioning system
│ └── <SelectItem> (Options)
│ ├── Checkmark indicator
│ └── Item text
└── Keyboard/Mouse handlingProps
Resolving labels in the trigger
<SelectValue> accepts three forms for the trigger label, in order of preference:
- No children — the registry wrapper defers to Base UI's native resolution. Pass an
itemsmap on<Select>to look up the label, otherwise the raw value is stringified. - Static children — render the same node regardless of value (rarely useful for selects).
- Render-prop children —
(value) => ReactNode. Returningnull/undefinedfalls back toplaceholder. Use this for rich trigger content (icon + label, avatar + name, status dot + label) that mirrors the selected item.
<Select items={{ '1': 'Any', '2': 'Read', '3': 'Write' }} value="1">
<SelectTrigger>
<SelectValue placeholder="Select operation" />
</SelectTrigger>
…
</Select>
// or, for rich content:
<SelectValue placeholder="Assign to teammate">
{(value) => {
const user = users.find((u) => u.value === value);
return user ? <UserContent user={user} /> : null; // null → placeholder
}}
</SelectValue>Examples
Scrollable
Groups
Disabled options
With description
With icon
Status indicator
Avatars
Custom rendering
Object values
Placeholder and clearable
Error state
Loading state
Enum-backed value (label ≠ String(value))
Base UI's <SelectValue> renders the raw value when the popup hasn't been opened yet — fine for selects whose label
matches String(value), broken for enum-backed selects (e.g. proto enums) where the trigger flashes 1 instead of
Any on first render. Two fixes, both shown in the demo: a render-prop on <SelectValue> (portable, no edge cases),
or an items map on <Select> (ergonomic when labels are static).
Form
In react-hook-form, drive <Select> with value={field.value} (controlled) and provide defaultValues: { name: '' }
on useForm. Using defaultValue={field.value} triggers Base UI's "uncontrolled default mutated after init" warning
the moment RHF rehydrates the field.
Keyboard interactions
| Key | Action |
|---|---|
Enter / Space / ArrowDown on SelectTrigger | Opens the listbox and focuses the currently selected item (or the first if none). |
ArrowUp / ArrowDown | Moves between options. |
Home / End | Jumps to first / last option. |
Enter | Selects the focused option and closes the listbox. |
Escape | Closes without changing selection. |
| Typing a character | Typeahead jumps to the next option whose label starts with that character. |
Accessibility
SelectTriggeris a real button witharia-haspopup="listbox"andaria-expanded; the listbox carriesrole="listbox"and each itemrole="option"witharia-selected.- Provide a visible label via
<Label htmlFor>or wrap the select in a<Field>so the selected value has a programmatic name. onValueChangeis the Radix-style single-argument(value: string) => void; the compat layer narrows Base UI's(value, details)signature.- For large option lists (>20), prefer
Comboboxso users can type-ahead-filter. Select's typeahead only jumps to matching first letters.
Credits
- We use Base UI for the select component.
- We take our inspiration from Shadcn UI for the select style.
Recent changes
- patchv1.2.0Pin shipped dependency floors to the version we develop against. Registry items now declare ranges like `^5.1.9` (the actual installed version) instead of collapsing to `^5.0.0`, so consumers start on the known-tested baseline while caret semantics still allow any compatible release within the same major.#133
- minorv1.1.0Theme docs refresh, readability pass on semantic foregrounds, and consumer-facing Base UI regression fixes.#121
- minorv1.0.0Post-Base-UI polish. Public API unchanged.#116
- majorv1.0.0Migrate every Radix-based primitive to `@base-ui/react@^1.4.0` (Base UI).#114
- patchv0.3.1## 🚧 Updated#112