v0.5

Select

Dropdown for choosing one value from a short list, with built-in keyboard, focus, and form-field composition.

Description

Select is a compound primitive built on top of Radix's Select. It composes Select, SelectTrigger, SelectContent, and SelectItem (with optional SelectValue, SelectGroup, SelectLabel, SelectSeparator, and SelectShell) into a fully-managed listbox popover. The trigger auto-wraps in SelectShell when standalone, and skips that wrapper when it detects an InputGroup or an explicit SelectShell ancestor so a single shared chrome can host multiple inputs.

Reach for it for short, mutually-exclusive value lists: country, currency, role, status, billing plan. It works as both a labelled form control (wrapped in Field) and a compact toolbar control. Size and validation state cascade from Field via context, so you set them in one place.

Don't use it for long searchable lists; use Combobox. For flat one-of-many choices where surfacing every option saves a click, use RadioGroup. For booleans, use Switch. For free-form input, use Input. The multi-select pattern shown below works fine for handfuls of values, but past ~10 options prefer a dedicated multi-select combobox.

Installation

pnpm dlx @create-ui/cli add select

Anatomy

SelectTrigger wraps itself in SelectShell automatically when used standalone. When it detects an InputShell (from InputGroup) or an explicit SelectShell ancestor, it skips its own chrome so the surrounding container owns the border, focus ring, and validation styles.

<Select>
  <SelectTrigger>
    <SelectValue />
  </SelectTrigger>
  <SelectContent>
    <SelectGroup>
      <SelectLabel />
      <SelectItem />
      <SelectSeparator />
    </SelectGroup>
  </SelectContent>
</Select>

Usage

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"
<Select>
  <SelectTrigger>
    <SelectValue placeholder="Pick one" />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value="a">Option A</SelectItem>
    <SelectItem value="b">Option B</SelectItem>
  </SelectContent>
</Select>

Examples

Variants

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

Sizes

Three sizes (xs, sm, md) match the rest of the form scale. sm is the default; pick md for hero forms and xs for dense settings tables.

States

Default, disabled, invalid, and loading. invalid tints the border and outline with error tokens; loading swaps the caret for a spinner and tints the chrome with info tokens. All four cascade from the surrounding Field if one is present.

With Icon

Render a leading element inside SelectTrigger to mirror the selected item's icon. Pass the placeholder icon when no value is set, and swap it for the active item's icon once a value is chosen. Drop a trailing element (e.g. a Badge) between SelectValue and the auto-rendered caret to surface a secondary label like a ticker or shortcut.

Controlled

Pass value and onValueChange to manage the state 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

Multi-select isn't a built-in mode of Radix's Select, but the primitive composes well: intercept onValueChange to toggle an array, intercept onOpenChange to keep the popover open after a click, and render the selection as Chips inside the trigger.

Accessibility

SelectTrigger and SelectContent inherit Radix's listbox keyboard 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 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:

  • Radix wires role="combobox" on the trigger, role="listbox" on the content, and role="option" on items, plus aria-expanded, aria-controls, aria-activedescendant, and aria-selected.
  • The component forwards invalid to aria-invalid on the trigger (and reads from Field context when unset). loading sets aria-busy="true".
  • For variant="compact" (icon-only trigger), always pass aria-label on SelectTrigger so the control announces its purpose.
  • For deeper focus, portal, and animation behavior, see Radix Select.

Styling

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

<SelectTrigger className="font-mono" />

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

  • data-slot="select" on the root, data-slot="select-shell" on the chrome wrapper, data-slot="select-trigger" on the trigger, data-slot="select-content" on the popover, data-slot="select-value" on the value, data-slot="select-group", data-slot="select-label", data-slot="select-item", data-slot="select-separator".
  • data-size="<size>", data-variant="<variant>", data-invalid, data-disabled, data-loading on both shell and trigger.
  • Radix-managed: data-state="open" | "closed" on trigger and content, data-side on the content, data-highlighted, data-state="checked" | "unchecked", and data-disabled on each item.

Target a state in CSS:

[data-slot="select-shell"][data-invalid] {
  /* … */
}
  • Combobox: searchable or async variant; use when the list is long or users need to filter.
  • RadioGroup: flat one-of-many selection always visible inline; use when there are 2–4 options and surfacing them saves a click.
  • NativeSelect: native <select> wrapper; reach for it when the form must work without JavaScript or you want the OS picker on mobile.
  • Input: free-text entry; use when values aren't constrained to a list.
  • InputGroup: the chrome wrapper used in the embedded-select pattern.

API Reference

Select

Wraps Radix's Select.Root and provides the SelectContext that sub-parts read for size, variant, and validation state. Extends React.ComponentProps<typeof SelectPrimitive.Root>, so all Radix root props (name, required, dir, etc.) pass through.

Props

PropTypeDefaultDescription
valuestring-Controlled selected value.
defaultValuestring-Uncontrolled initial value.
onValueChange(value: string) => void-Fires when the user selects an item.
openboolean-Controlled open state of the listbox.
defaultOpenboolean-Uncontrolled initial open state.
onOpenChange(open: boolean) => void-Fires when the listbox opens or closes.
disabledboolean-Disables the control; cascades through context.
invalidboolean-Marks the control invalid; sets aria-invalid and error styling.
loadingbooleanfalseDisables interaction, swaps the caret for a spinner, and tints the chrome info.
namestring-Form field name for native form submission.
requiredboolean-Marks the field as required for native form submission.
childrenReact.ReactNode-The trigger and content sub-parts.

Variants

VariantOptionsDefaultDescription
size"xs" "sm" "md""sm"Cascades to trigger, shell, content, label, and items.
variant"default" "compact""default"default is full-width; compact shrinks to fit (icon triggers).

SelectTrigger

Wraps Radix's Select.Trigger and auto-wraps itself in SelectShell when it doesn't detect a parent shell. Extends Radix Select.Trigger props (asChild, aria-label, aria-labelledby, etc.).

Props

PropTypeDefaultDescription
sizeSelectSize-Override the context size. Rarely needed.
variantSelectVariant-Override the context variant. Rarely needed.
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-A leading element (icon) and a SelectValue.

SelectValue

Wraps Radix's 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.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Override the rendered label for the selected value.

SelectShell

Manual chrome wrapper for hand-rolled trigger layouts (e.g. composing a select with another input). Extends React.ComponentProps<"div">. Reads size, variant, and state from SelectContext if present; falls back to sm / default otherwise.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-The trigger plus any sibling inputs that share the same chrome.

Variants

VariantOptionsDefaultDescription
size"xs" "sm" "md""sm"Height and radius of the shell.
variant"default" "compact""default"default is full-width (w-full); compact is w-fit.
invalidbooleanfalseApplies error border and outline tokens.
disabledbooleanfalseApplies disabled background and pointer-events lockout.

SelectContent

Wraps Radix's Select.Content rendered inside Select.Portal. Extends React.ComponentProps<typeof SelectPrimitive.Content>.

Props

PropTypeDefaultDescription
position"item-aligned" | "popper""popper"Listbox positioning strategy.
align"start" | "center" | "end""start"Horizontal alignment relative to the trigger.
side"top" | "right" | "bottom" | "left"-Preferred side; Radix flips automatically when space is tight.
sideOffsetnumber-Distance in pixels from the trigger.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-SelectGroup, SelectItem, SelectLabel, and SelectSeparator parts.

SelectGroup

Wraps Radix's Select.Group. Use it to group related items, optionally with a SelectLabel header.

SelectLabel

Wraps Radix's Select.Label. Sized by the surrounding SelectContext.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Label text rendered above the item group.

SelectItem

Wraps Radix's Select.Item. The first child (if more than one is passed) becomes the leading element; the remaining children become the item text. A trailing check mark is rendered from Select.ItemIndicator when the item is selected.

Props

PropTypeDefaultDescription
valuestring-The value committed when the item is selected.
disabledboolean-Disables the item; it is skipped by keyboard navigation.
textValuestring-Override the string used for type-ahead matching.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Optional leading element followed by the item label.

SelectSeparator

Wraps Radix's Select.Separator. A thin bg-weak divider for splitting groups inside the content.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().