Skip to content

Select

Choose one option from a list, presented in a popover.

Stablev0.4.2added in v0.1.0@garn/ui/select
On this page

Default

Trigger + placeholder + three options.

Sizes

xs · sm · md · lg · xl trigger heights.

Grouped

Grouped options with labels and a separator.

Invalid

aria-invalid — danger edge + danger focus ring (shared field surface).

Disabled

A disabled trigger.

Installation

garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.

terminal
npx garn-ui add select
First time? Set up garn in your project
Registry
select
Deps
@radix-ui/react-selectclass-variance-authoritylucide-react
Registry deps
utilsfield-variants

Import

import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGroup, SelectLabel, SelectSeparator } from "@garn/ui/select";

Anatomy

The parts this component is built from — compose them to assemble it.

SelectRequired

State container (Radix Root) — owns value + open. No DOM of its own.

SelectTriggerRequired[data-slot="select-trigger"]

The button that opens the listbox; renders the shared field surface (carries the appearance + size variants).

SelectValue[data-slot="select-value"]

Renders the selected value, or the placeholder when empty.

SelectContentRequired[data-slot="select-content"]

The portaled popover listbox.

SelectItemRequired[data-slot="select-item"]

A selectable option. Requires a unique, non-empty value.

SelectGroup

Groups related items under a SelectLabel.

SelectLabel[data-slot="select-label"]

A heading inside a group (not the field's label).

SelectSeparator[data-slot="select-separator"]

A divider between groups.

Props

The component's public props and their types.

appearance
Description
No description.
Type
"ghost" | "outline" | "soft"
Default
outline
size
Description
No description.
Type
"lg" | "md" | "sm" | "xl" | "xs"
Default
md

Plus 13 inherited native HTML attributes.

Styling

Target these data-slots and states, and remap these tokens, to restyle without forking the component.

[data-slot="select-trigger"]

States

SelectorState
:hoverhover
:focusfocus
[data-state=open]open
[data-state=checked]item-selected
[data-highlighted]item-highlighted
[aria-invalid=true]invalid
[disabled]disabled
PropertyTokenTier
triggerHeight--garn-control-h-mdsemantic · air
triggerPaddingInline--garn-control-px-mdsemantic · air
itemMinHeight--garn-control-h-mdsemantic · air
borderColor--garn-foregroundsemantic

When to use

Pick one option from a medium-to-long list (~5+) where the choices fit in a popover.

Reach for something else when

  • A few options where seeing them all helps (use radio-group)
  • free text (use input)
  • choosing many (Select is single-select — use a command / multi-select pattern).

Overview

Select lets a person pick one option from a list that's too long to show inline but short enough to scan in a popover (roughly 5–15 choices). It's a compound component: Select owns the value and open state, SelectTrigger is the button you see (it renders the shared field surface, so it carries the same appearance × size variants as Input and lines up with the rest of a form), and SelectContent is the portaled listbox of SelectItems — optionally organized with SelectGroup + SelectLabel.

Because it's built on Radix, the keyboard and ARIA model — typeahead, arrow navigation, aria-activedescendant, focus return — comes for free. Reach for Select when the choices are predefined and the user just needs to pick; if they need to search a long list, that's a Combobox, and if there are only two-to-five always-visible options, a RadioGroup reads better.

Guidelines

Give every SelectItem a unique, non-empty value. An empty string collides with the placeholder slot and will throw; use a real key. Keep the visible label short — the trigger truncates, and a wall of long options is hard to scan.

Use a placeholder for the empty state, a label for the name. SelectValue's placeholder says what to pick ("Choose a country"); it is not a substitute for an external label. Wire the field's accessible name with a Label or Form, the same as any other control.

Group only when it earns its keep. SelectGroup + SelectLabel help when options fall into a few clear buckets; for a flat list they add noise. Don't nest a Select inside another popover/menu — if you're tempted, the pattern probably wants a Command palette or a Combobox instead.

Best practices

Do
  • Label the trigger; give SelectValue a clear placeholder for the empty state.
  • Give each SelectItem a unique, non-empty value.
  • Group long lists with SelectGroup + SelectLabel.
Don't
  • Don't rely on the placeholder as the field's label.
  • Don't use a Select for 2–4 options the user benefits from seeing at once — that's radio-group.
  • Don't expect multi-select — Select is single-value.

Content guidelines

  • Write a placeholder that names the choice (“Select a timezone”), not a generic “Select…”.
  • Keep option labels short, parallel, and in a sensible order; group long lists with SelectLabel.
  • Label the trigger separately — the placeholder isn't a substitute for a label.

Troubleshooting

Runtime error or an option that can't be selected.

Cause. A SelectItem with an empty value — Radix reserves the empty string to clear the selection.

Fix. Give every SelectItem a unique, non-empty value.

Screen readers announce the field with no name.

Cause. The SelectValue placeholder is not an accessible label.

Fix. Associate a <label> with the trigger, or pass aria-label to SelectTrigger.

Accessibility

Role
combobox
ARIA APG
combobox
Focus
Trigger border darkens (foreground/40) on focus; keyboard focus adds a 2px neutral ring (foreground/15). Focus moves into the listbox on open and returns to the trigger on close.

Accessibility requirements

warn

Give the SelectTrigger an accessible label (a <label>, or aria-label/aria-labelledby).

when SelectTrigger has no associated <label>, aria-label, or aria-labelledby (the placeholder doesn't count)

Keyboard

SpaceEnterArrowDownArrowUpOpen the listbox.
ArrowUpArrowDownHomeEndMove between options.
A–ZType-ahead to the matching option.
EnterSelect the highlighted option and close.
EscapeClose without changing the value.
A1.3.1Info and RelationshipsA2.1.1KeyboardA4.1.2Name, Role, ValueAA2.4.7Focus Visible
  • Trigger exposes aria-expanded / aria-controls; the selected option has aria-selected.
  • The placeholder is NOT an accessible name — label the trigger explicitly.