Skip to content

Select

Stable

Lets people choose one option — or several — from a list that stays out of the way until it's needed.

Anatomy

  1. 1TriggerLooks like an input and shares its heights, so a select lines up with text fields in a form.
  2. 2ValueThe selected option's label — or a placeholder in muted text when nothing is chosen.
  3. 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.json

Usage

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.

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.

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).

Instance sizeSmall · 1 vCPUMedium · 2 vCPULarge · 4 vCPU
Do.Options ordered by magnitude, with short, parallel labels.
large-4cpuSmall instance (1 vCPU)MEDIUM
Don’t.Arbitrary order and inconsistent labels make people read every option.

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.

KeyBehavior
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 FieldLabel at the trigger with htmlFor, or give the trigger an aria-label when the context makes the purpose obvious visually (for example a toolbar).
  • Boundaries. The trigger's border uses the input token, 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} on Select if the page must stay interactive.

API reference

Select

The root. Doesn't render an element. Accepts every prop of Base UI's Select.Root.

PropTypeDefault
items

The options' labels. Lets SelectValue render a label instead of a raw value; a null-valued item provides the placeholder.

Record<string, ReactNode> | { label, value }[] | Group[]No default
value

The selected value. Use with onValueChange for a controlled select.

Value | Value[] | nullNo default
defaultValue

The initially selected value when uncontrolled.

Value | Value[] | nullNo default
onValueChange

Called when the selection changes.

(value, details) => voidNo default
multiple

Allows several options to be selected; the value becomes an array.

booleanfalse
itemToStringValue

Serializes object values for form submission.

(value) => stringNo default
itemToStringLabel

Turns object values into text for the trigger.

(value) => stringNo default
isItemEqualToValue

Custom equality for object values. Defaults to Object.is.

(itemValue, value) => booleanNo default
name

Submits the value under this name with a form.

stringNo default
required

Requires a value before the form can be submitted.

booleanfalse
disabled

Disables the whole select.

booleanfalse
readOnly

Shows the value but prevents changing it.

booleanfalse
open / defaultOpen / onOpenChange

Controls the popup's visibility.

boolean / boolean / (open, details) => voidNo default
modal

Locks page scroll and outside interaction while open.

booleantrue

SelectTrigger

The button that shows the value and opens the list. Renders a <button>.

PropTypeDefault
size

Height and padding: 32, 36 or 40px — shared with Input and Button.

"sm" | "default" | "lg""default"
aria-invalid

Shows the error state. Pair with data-invalid on the surrounding Field.

booleanNo default

SelectValue

Displays the selection inside the trigger. Renders a <span>.

PropTypeDefault
children

Custom rendering of the selected value, e.g. a summary for multiple selection.

ReactNode | (value) => ReactNodeNo default
placeholder

Shown when nothing is selected. A null-valued item in items takes precedence.

ReactNodeNo default

SelectContent

The positioned popup and its scrollable list.

PropTypeDefault
alignItemWithTrigger

Overlaps the trigger so the selected option sits on top of it. Set false for a dropdown that opens beside the trigger.

booleantrue
side

Preferred side when not aligned with the trigger.

"top" | "bottom" | "left" | "right""bottom"
sideOffset

Gap between trigger and popup, in pixels.

number6
align

Alignment along the side.

"start" | "center" | "end""center"
alignOffset

Offset along the alignment axis, in pixels.

number0

SelectItem

An option. Renders a <div> with role="option".

PropTypeDefault
valuerequired

The value this option represents.

anyNo default
disabled

Prevents highlighting and choosing the option.

booleanfalse
label

Text used for typeahead when the content isn't plain text.

stringNo default

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.