Select
A closed field that opens a list of options and holds one of them. Use it when the list is too long to show at once; when two or three options fit on screen, a radio button group shows them without a click.
Live preview
Rendered by the component itself from the same generated tokens as the rest of the site.
<Select
label="Region"
options={[
{ value: 'eu', label: 'Europe' },
{ value: 'na', label: 'North America' },
{ value: 'sa', label: 'South America' },
{ value: 'apac', label: 'Asia Pacific' },
]}
value="eu"
helpText="Where your data is stored."
onChange={setRegion}
/>Installation
Install it with the shadcn CLI, along with everything it uses. New project? Set it up first, as Quick start shows.
npx shadcn@latest add https://www.graphite-ui.com/r/select.jsonimport { Select, type SelectOption } from '@/components/ui/select'Anatomy
The closed trigger is Select’s own, on the field shell. The option list it opens is Dropdown’s, on the overlay surface.
- LabelRequiredThe control renders it itself. There is no wrapper to take it from — the kit builds label text into each form control.
- Option listRequiredMinimum 2 options.
- Supporting textOptionalHelp text, or error text. The kit calls this Helper / Error text and builds it into the control the same way.
Variants
Size shares its scale with Text input, so a select and the inputs beside it line up. Layout gives the kit’s Default, Inline and Fluid shapes.
States
Disabled and error are values of the state prop. Hover and focus are pseudo-classes, so the page forces them here. Hover steps the fill to elevation-02, the kit’s field hover. Error and Warning draw the kit’s status icon before the chevron. Open is not drawn here: open the live preview to see the list.
API reference
Generated from the contract, versioned with it, and checked by drift-check. This table cannot describe props the component does not have.
| Prop | Type | Default | Notes |
|---|---|---|---|
| size | 'sm' | 'md' | 'lg' | — | |
| state | 'default' | 'disabled' | 'error' | 'warning' | — | |
| layout | 'fixed' | 'inline' | 'fluid' | — | The kit's Style axis (Default, Inline) and its Select - Fluid set. Inline's trigger has no fill and no edge at rest and hugs its value; Fluid is a 64px box with the label inside. |
| hideLabel | boolean | — | The kit's bare Select menu, which draws only the value (Pagination's page picker, #268). The label stays the accessible name, hidden from view; it is still required. |
| readOnly | boolean | — | The kit's Read-only. The trigger stays focusable and carries aria-readonly while refusing to open or change. It must not trade away keyboard focus. |
| label | — | — | Required. There is no shape in which this control exists unlabelled, and no wrapper left to supply one. |
| helpText | — | — | Supporting copy. Suppressed while errorText or warningText is present. |
| warningText | — | — | Resolves the warning state, outranked by an error. |
| errorText | — | — | Its presence resolves the error state, so error text and error styling cannot be shown apart. This was Field's guarantee and it survives Field. |
Design tokens
Every swatch is live. Change the source color in the header and this table repaints, because it reads the same roles the component does.
elevationThe field's fill, elevation-01, the kit's Field/field-01. Select's hover rung, elevation-02, comes through the same inheritance. (from Text input)outlineThe bottom rule at rest, through its strong step (outline-strong), which is also the placeholder colour; read-only quietens it to the subtle step. A rule on the bottom only, never a box. (from Text input)primaryThe focus ring, through the family's focus step: 2px inside all four sides. (from Text input)dangerThe error ring, 2px inside, the error text, and the error status icon's triangle. (from Text input)warningThe warning status icon's triangle. (from Text input)on-warningThe "!" on the warning status icon. (from Text input)backgroundThe "!" on the error status icon, which the kit cuts out of the triangle to show what is behind it. (from Text input)on-surfaceValue text. (from Text input)textThe value at Body/3 at every size; size changes the height, never the type. (from Text input)spacingField padding and height steps. (from Text input)radiusField corner. Inherited by Select and Text area. (from Text input)on-surface-variantLabel and helper text, which the kit binds to onSurfaceVariant rather than onSurface.
Usage
The contract's prohibitions, written as the choices you will actually face.
Do
- Use a select for a long list of choices where only one can hold, such as a country or a region.
- Match its size to the text inputs in the same form. The two share one size scale.
- Show an error with errorText, so the red ring always comes with the reason for it.
- Keep at least two options. The type rejects fewer, because a select with one choice is a statement.
Don’t
- Restyle the open list for one select. It is Dropdown’s, shared by both, so a change belongs there.
- Reach for Dropdown just to get a styled list. Select opens the same one; Dropdown is for its own sets and the combo box and multi-select kinds.
- Use a select for two or three options that fit on screen. A radio button group shows them without a click.
- Set state="error" without errorText. The ring says something is wrong without saying what.
Accessibility
What the component does for you, and what it leaves to you.
- Keyboard
- Tab reaches the trigger. Arrow Down, Arrow Up, Enter or Space open the list; the arrows, Home and End move; Enter or Space chooses; Escape closes; typing jumps to a matching option. Focus stays on the trigger throughout.
- Roles
- The ARIA select-only combobox: the trigger is a
combobox, the list alistboxofoptions, and the keyboard’s row is itsaria-activedescendant. In error it carriesaria-invalid; read-only carriesaria-readonlyand stays focusable, but will not open or change. - Labels
- The label and the value name the trigger through
aria-labelledby, and a click on the label focuses it. Help and error text are linked witharia-describedby, and error text is announced as it appears. - Focus
- A 2px ring inside the trigger in
primary’s focus colour while the field has focus, replacing the browser outline. In error it staysdanger. - Mobile
- Touch opens the same list, its rows at the trigger’s height. The phone’s own picker no longer appears: 3.0.0 traded it for a list that takes the kit’s look.
Figma parity
The kit's Select page ships 74 variants across 6 sets. The code exposes 9 props. This table is where those two facts are reconciled instead of quietly diverging.
| Kit axis | Values | Code | How it maps |
|---|---|---|---|
| Size | Small · Medium · Large | size | sm, md and lg. One to one. |
| Style | Default · Inline | layout | Both. Inline’s trigger has no fill or edge at rest and hugs its value, with the label and message beside it. |
| State | Enabled · Disabled · Error · Warning | state | Error and Warning follow errorText and warningText and draw the status icon 8 before the chevron. Disabled fills the trigger with the disabled tone, as Select’s own set draws it. |
| State | Read-only | readOnly | The trigger stays focusable with aria-readonly and refuses to open or change. No fill, a plain outline rule. |
| State | Focus · Hover | — | Pseudo-classes (governance rule 7). Hover is :hover on the trigger and steps the fill to elevation-02. Focus is :focus-within. |
| Open | False · True | — | Runtime state, not a prop: the trigger opens Dropdown’s list, which is how the kit draws Open. |
| State | Skeleton | — | No counterpart by rule. |
| Set | Select - Fluid | layout="fluid" | A 64px box, the label inside, the value row at 18, a plain outline rule. |
| Trigger value | Empty in every variant but Read-only | — | The kit draws no value in the trigger except in Read-only, a slip; the code shows the value, styled as Read-only draws it. |
