v1.0

Select

Form-bound value picker built on React Aria, from a plain one-of-many list to searchable, grouped, rich, and multi-select pickers.

Pro

Description

Select is a compound value picker built on React Aria's Select. It composes a Select.Trigger (with Select.Value), a Select.Popover surface, and a list of Select.Item rows, with optional Select.Group, Select.Label, Select.Search, Select.Shell, and Select.Separator slots. The list, items, and section headings are the same surface as Dropdown and Combobox, so a row looks identical across all three. size is set on the root and cascades through context to the trigger, items, labels, and icons.

Reach for it for a value the form submits: country, currency, role, status, plan, payment method. It binds to a Field for its label and its size/invalid/disabled/loading cascade, and drops into an InputGroup as a compact leading picker. Pass selectionMode="multiple" and it becomes a multi-select you render as chips. Add a header={<Select.Search />} to the popover and the panel filters as you type, without turning the trigger into a text field.

The free tier covers the trigger, plain Select.Item rows, the default and compact variants, the three sizes, the invalid/disabled/loading states, controlled usage, the input-group embed, and the chip multi-select. Smart items (a leading media slot, a secondary description, a trailing slot), grouped Select.Group + Select.Label, the pinned Select.Search bar, the footer, and the rich multi-select picker are Pro; the live demos still run, but the source needs a Pro seat. Do not use a select for a long list users must filter to navigate (use Combobox), for a menu of commands rather than a value (use Dropdown), for a handful of always-visible options (use Radio Group), or for a boolean (use Switch).

Installation

pnpm dlx @create-ui/cli add select

Anatomy

<Select>
  <Select.Trigger>
    <Select.Value />
  </Select.Trigger>
  <Select.Popover header={<Select.Search />}>
    <Select.Group>
      <Select.Label />
      <Select.Item />
      <Select.Separator />
    </Select.Group>
  </Select.Popover>
</Select>

Usage

import { Select } from "@/components/ui/select"
<Select value={value} onChange={setValue}>
  <Select.Trigger>
    <Select.Value placeholder="Pick one" />
  </Select.Trigger>
  <Select.Popover>
    <Select.Item value="a">Option A</Select.Item>
    <Select.Item value="b">Option B</Select.Item>
  </Select.Popover>
</Select>

Examples

The free tier covers the trigger, plain items, variants, sizes, states, controlled usage, the input-group embed, and the chip multi-select. Richer previews (smart items, search, grouped sections, the rich user picker) are marked with a Pro badge; the live demo still runs, but the source needs a Pro seat.

Variants

variant="default" is a full-width trigger with placeholder text, the form-field shape. variant="compact" shrinks the trigger to fit its content for toolbar pickers and icon-only controls; pair it with aria-label on Select.Trigger.

Sizes

Three sizes (xs, sm, md) set on the root cascade to the trigger, item height, label, and icon size. sm is the default; pick md for hero forms and xs for dense settings tables.

States

Default, isDisabled, isInvalid, and loading. isInvalid tints the border and outline with error tokens; loading swaps the caret for a spinner and locks the field with info tokens. All of them cascade from a surrounding Field.

With icon and badge

Render a leading element inside Select.Trigger to mirror the selected item's icon, and drop a trailing element (such as a Badge) between Select.Value and the auto-rendered caret to surface a secondary label like a ticker.

Controlled

Pass value and onChange to manage the selection yourself. Use uncontrolled defaultValue for simple forms; reach for controlled when other UI needs to react to the selection.

Selected:

Inside an input group

Drop a Select variant="compact" next to an Input inside InputGroup and the trigger detects the surrounding InputShell, skips its own chrome, and lets the group own the single shared border and focus ring.

Multi-select

Pass selectionMode="multiple" with a value array. Intercept onChange to keep an array in state and render the selection as Chips inside the trigger; the popover stays open between clicks.

Rich items

A smart item takes a leading icon, a secondary description line, and an indicator for the selected check. The item surface is shared with Dropdown, so the row looks identical.

Pro

Searchable token picker

Pass header={<Select.Search />} to Select.Popover and the panel filters as you type against each item's textValue, while the trigger stays a value display. Grouped Your assets / Popular sections hold rich items with a brand icon, a price description, and a trailing holdings amount, with an empty state and an error footer.

Pro
Select Token

Searchable card picker

Two-line items (a brand mark leading, a masked number description) behind a Select.Search, with a no-match empty state and a footer action to add a new card.

Pro
Select Card
Make sure your selection is correct

Multi-select user picker

A multi-select assignee list: a leading Checkbox beside member avatars (composed from the item sub-parts), the selection shown as chips with a +N more overflow in the trigger, and an apply / clear footer.

Pro
Select Users
This selection affects access and permissions

Insert block

A command-style select with a Pro upsell: grouped items with a selection check, a separated AI Block row, and a footer pairing an "Unlock with Pro" badge with an upgrade button.

Pro

Accessibility

Select inherits React Aria's select and listbox interaction model. The trigger is a single tab stop; arrow keys, type-ahead, and Home / End operate inside the open listbox.

KeyDescription
Space EnterOpens the listbox; confirms the focused item.
Moves the highlight; opens the listbox when closed.
Home EndJumps to the first / last item.
A-ZType-ahead; focuses the first item matching the prefix.
EscCloses the listbox without changing the value.
TabMoves focus away; does not commit a highlight.

ARIA notes:

  • React Aria wires role="button" with aria-haspopup="listbox" on the trigger, role="listbox" on the popover, and role="option" on items, plus aria-expanded, aria-activedescendant, and aria-selected.
  • The root forwards isInvalid to aria-invalid on the trigger (and reads it from Field context when unset); loading sets aria-busy="true" and disables the control.
  • For variant="compact" (an icon-only trigger), always pass aria-label on Select.Trigger so the control announces its purpose.
  • The searchable variant wraps the panel in a React Aria Autocomplete: the Select.Search input keeps DOM focus while arrow keys drive virtual focus in the listbox, and items filter against their textValue. Provide renderEmptyState so a no-match hint can show.
  • For deeper focus, portal, and filtering behavior, see the React Aria Select docs.

Styling

Tailwind override: pass className to merge Tailwind classes on the trigger, the popover, or any sub-part (merged via cn()):

<Select.Popover className="w-72" />

Data slots and attributes: the component sets these for CSS targeting:

  • data-slot="select" on the root, select-shell on the chrome wrapper, select-trigger on the trigger, select-value on the value, select-content on the popover, select-listbox on the list, select-search on the pinned search bar, select-group on a group, select-label on a heading, select-item on a row, select-item-trailing on the trailing slot, and select-separator on a divider. The item internals reuse the dropdown slots: dropdown-item-container / dropdown-item-content / dropdown-item-label / dropdown-item-description / dropdown-item-indicator, plus dropdown-footer.
  • data-size="xs" | "sm" | "md", data-variant="default" | "compact", and data-invalid / data-loading / data-placeholder mirror the resolved state on the shell and trigger.
  • The trigger carries data-state="open" | "closed"; the searchable trigger reuses the Input shell so its focus-within, invalid, disabled, and loading borders are identical to a plain Input.
  • React Aria manages data-focused, data-selected, and data-disabled on each item; wrappers style through group-data-[focused]/item and group-data-[selected]/item.

Target a state in CSS:

[data-slot="select-item"][data-selected] {
  /* … */
}
  • Combobox: the same list surface with a text-input trigger, for long lists you filter as you type or values that might not be in the list.
  • Dropdown Menu: the same list surface opened from a button, for commands and actions rather than a value.
  • Radio Group: flat one-of-many selection kept inline; use it for 2-4 options where surfacing every choice saves a click.
  • Native Select: a native <select> wrapper; reach for it when the form must work without JavaScript or you want the OS picker on mobile.
  • Input Group: the chrome wrapper used in the embedded-select pattern.

API Reference

Select.* reuses the Dropdown/Combobox item surface, so item props match Dropdown.Item. The select-specific parts are the root, Select.Trigger, Select.Value, Select.Shell, Select.Popover, and Select.Search.

Select

The root. Wraps React Aria's Select, so its selection and collection props pass through. Provides the size/variant/state context.

Props

PropTypeDefaultDescription
valueKey | Key[] | null-Controlled selection (Key for single, Key[] for multiple).
defaultValueKey | Key[] | null-Uncontrolled initial selection.
onChange(value) => void-Fires when the selection changes.
selectionMode"single" | "multiple""single"Turns the listbox into a multi-select.
isOpenboolean-Controlled open state of the listbox.
onOpenChange(isOpen: boolean) => void-Fires when the listbox opens or closes.
loadingbooleanfalseSwaps the caret for a spinner and locks the field. Falls back to Field.
isDisabledbooleanfalseDisables the control. Falls back to a surrounding Field.
isInvalidbooleanfalseInvalid styling and aria-invalid. Falls back to a surrounding Field.
namestring-Form field name for native submission.
childrenReact.ReactNode-The trigger and the Select.Popover.

Variants

VariantOptionsDefaultDescription
size"xs" "sm" "md""sm"Cascades to the trigger, item height, label, and icon size. Falls back to Field.
variant"default" "compact""default"default is full-width; compact shrinks the trigger to fit (icon/toolbar pickers).

Select.Trigger

The button that opens the listbox and shows the value. Wraps a leading element and a Select.Value, and auto-renders the trailing caret (or a spinner while loading). Standalone it wraps itself in Select.Shell; inside an InputGroup or a Select.Shell it renders bare so the container owns the chrome.

Props

PropTypeDefaultDescription
aria-labelstring-Accessible name for a compact (icon-only) trigger.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-A leading element and a Select.Value.

Select.Value

Renders the selected item's label, or the placeholder when nothing is selected.

Props

PropTypeDefaultDescription
placeholderReact.ReactNode-Shown when no value is selected.
childrenReact.ReactNode-Override the rendered label for the selected value.
classNamestring-Tailwind classes merged via cn().

Select.Shell

Manual chrome wrapper for hand-rolled trigger layouts (for example composing a select beside another input). Reads size, variant, and state from context. Extends React.ComponentProps<"div">.

Select.Popover

The floating surface (alias Select.Content). Renders the ListBox. Extends React Aria's PopoverProps; defaults to placement="bottom start" and offset={8}. Providing header wraps the panel in an Autocomplete so a Select.Search inside it filters the list.

Props

PropTypeDefaultDescription
headerReact.ReactNode-Pinned bar above the list; pass <Select.Search /> for filtering.
footerReact.ReactNode-Pinned bar below the list, with a top divider.
filter(textValue: string, inputValue: string) => booleancontainsCustom match for the searchable variant.
renderEmptyState() => ReactNode-Rendered inside the list when it has no items (no filter match).
placementPlacement"bottom start"Preferred side/alignment relative to the trigger.
offsetnumber8Distance in pixels from the trigger.
classNamestring-Tailwind classes merged via cn() (commonly a width).

Select.Search

A pinned search field for the searchable variant. Pass it as header on Select.Popover. Reuses the Input shell.

Props

PropTypeDefaultDescription
placeholderstring"Search"Placeholder text for the field.
iconReact.ReactNodesearchLeading icon; pass null to omit.
classNamestring-Tailwind classes for the field row.
inputClassNamestring-Tailwind classes for the inner input element.

Select.Item

A list row. A plain label with an optional leading media, value (alias for id), and textValue are free; description, trailing, and indicator opt into the smart layout (Pro). React Aria ListBoxItem props (isDisabled, and a render-function child receiving { isSelected }) pass through. The type-ahead and search filter match against textValue.

Props

PropTypeDefaultDescription
valueKey-The value committed when selected (alias for id).
leadingReact.ReactNode-Leading media (icon / flag / Avatar).
descriptionReact.ReactNode-Secondary line under the label.
trailingReact.ReactNode-Trailing slot (amount / badge); coexists with the selection check.
indicatorReact.ReactNode-Content of the selection indicator (defaults to a check). Pass null to suppress.
textValuestring-String used for type-ahead and search when children are not plain text.
isDisabledboolean-Disable this row.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-The row label, or a render function receiving { isSelected, ... }.

Other parts

PartRole
Select.Group / SectionGroups related items under a shared label (Pro).
Select.LabelThe overline heading at the top of a group (Pro).
Select.SeparatorA thin divider between runs of items.
Select.MiscPins static content above the list.
Select.ItemContainerWraps the media and text block of a row.
Select.ItemContentStacks the label and description.
Select.ItemLabelThe row's primary text.
Select.ItemDescriptionThe secondary line beneath the label.
Select.ItemIndicatorThe selection check shown on the selected row.