Select
StableLets people choose one option — or several — from a list that stays out of the way until it's needed.
Anatomy
- 1TriggerLooks like an input and shares its heights, so a select lines up with text fields in a form.
- 2ValueThe selected option's label — or a placeholder in muted text when nothing is chosen.
- 3IconUp and down chevrons: the list opens over the trigger with the selected option in place.
Installation
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/select.jsonUsage
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"const regions = [
{ label: "Select a region", value: null },
{ label: "Frankfurt", value: "fra1" },
{ label: "Tokyo", value: "hnd1" },
]
<Select items={regions}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="fra1">Frankfurt</SelectItem>
<SelectItem value="hnd1">Tokyo</SelectItem>
</SelectGroup>
</SelectContent>
</Select>Pass items to the root. It lets SelectValue render the label of the selected option instead of its raw value, and an item with value: null becomes the placeholder shown while nothing is selected. Render that placeholder item in the list only when clearing the selection is a real choice — and then label it as one, for example No role.
Examples
Groups
Group long lists under labels and divide the groups with separators. Keep labels short nouns; they are headings, not options, and can't be selected.
Sizes
The trigger comes in sm, default and lg — the same 32, 36 and 40px heights as Input and Button, so a filter bar lines up without a single custom class.
Item-aligned and popper positioning
By default the list opens aligned with the trigger: the selected option sits exactly over the trigger, like a native macOS menu, so the eye doesn't have to travel. Set alignItemWithTrigger={false} on SelectContent for a classic dropdown that opens below — better near the bottom of a viewport, inside dense toolbars, or whenever covering the trigger would hide context.
The selected item opens over the trigger, like a native menu.
The list drops below the trigger and never covers it.
Multiple selection
Set multiple and give defaultValue an array. Pass a function to SelectValue to decide how many selections to spell out before summarizing — long comma-separated lists get truncated and stop being readable.
<Select items={channels} multiple defaultValue={["email", "slack"]}>
<SelectTrigger>
<SelectValue>
{(value: string[]) =>
value.length === 1 ? labelFor(value[0]) : `${value.length} channels`
}
</SelectValue>
</SelectTrigger>
…
</Select>Rich options and object values
Options can carry more than a label. When values are objects, tell the select how to serialize them for forms with itemToStringValue, and render the selection yourself through SelectValue's render function. Let the trigger grow with h-auto!.
With icons and disabled options
Leading visuals — status dots, flags, avatars — help people scan. Put them in both the option and the value render function so the trigger reflects the choice. Disabled options stay visible so people learn they exist, but can't be highlighted or chosen.
In a form
Compose with Field for the label, description and error. FieldLabel's htmlFor points at the trigger's id; name and required submit and validate the value like a native control. Mark the trigger aria-invalid and the field data-invalid to show an error.
Used for emails and the dashboard.
Guidelines
When to use
- To choose from five to fifteen mutually exclusive options that don't need to be compared side by side.
- When space is tight and the current choice is more important than the alternatives.
When not to use
- For two to five options that fit on screen — show them all with a Radio Group or Toggle Group; hiding them behind a click slows people down.
- For long lists people need to search — use a Combobox.
- To trigger actions such as Duplicate or Delete — use a Dropdown Menu. A select holds a value; a menu runs a command.
- When the platform's native picker is preferable, for example on mobile-heavy forms — use Native Select.
Order and wording
Order options the way people think about them: by frequency, by magnitude (Small, Medium, Large) or alphabetically — never by the order they were added to the database. Keep labels short and parallel, and make the placeholder describe the action (Select a region), not the field (Region).
Defaults
Preselect a value when there's a safe, common answer — it saves a decision for most people. Leave the select empty when choosing wrong has consequences (a billing country, a permission level), so the choice is deliberate and validation can catch an omission.
Accessibility
The trigger is a button with role="combobox" that controls a listbox popup. Base UI manages focus, aria-expanded, keyboard highlighting and typeahead, and submits the value through a hidden input.
| Key | Behavior |
|---|---|
EnterSpace↓↑ | Opens the list with the selected option highlighted. With the list open, Enter or Space chooses the highlighted option. |
↓↑ | Moves the highlight between options, skipping disabled ones. |
HomeEnd | Highlights the first or last option. |
A–Z | Typeahead: jumps to the next option starting with the typed characters. |
Esc | Closes the list without changing the value and returns focus to the trigger. |
Tab | Closes the list and moves focus to the next control. |
- Label every select. Point a visible
FieldLabelat the trigger withhtmlFor, or give the trigger anaria-labelwhen the context makes the purpose obvious visually (for example a toolbar). - Boundaries. The trigger's border uses the
inputtoken, which meets 3:1 against the page for WCAG 1.4.11. Selected and highlighted states never rely on color alone: the selected option carries a check mark. - Target size. Options are at least 32px tall and the trigger at least 32px, above the 24×24px minimum of WCAG 2.2.
- Modality. The open list is modal by default: page scroll locks and outside clicks close it first. Set
modal={false}onSelectif the page must stay interactive.
API reference
Select
The root. Doesn't render an element. Accepts every prop of Base UI's Select.Root.
SelectTrigger
The button that shows the value and opens the list. Renders a <button>.
SelectValue
Displays the selection inside the trigger. Renders a <span>.
SelectContent
The positioned popup and its scrollable list.
SelectItem
An option. Renders a <div> with role="option".
SelectGroup, SelectLabel, SelectSeparator
SelectGroup groups options (and is required around them), SelectLabel titles a group, and SelectSeparator draws a divider between groups. They accept the props of their Base UI counterparts.