Skip to main content
Graphite UI

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.

Contract 3.0.0Kit · 6 sets · 74 variantsOpen in Figma

Live preview

Rendered by the component itself from the same generated tokens as the rest of the site.

RegionWhere your data is stored.

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.json
import { 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.

LabelSupporting text sits under the field.
  1. LabelRequiredThe control renders it itself. There is no wrapper to take it from — the kit builds label text into each form control.
  2. Option listRequiredMinimum 2 options.
  3. 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.

Size: Small
Label
Size: Medium
Label
Size: Large
Label
Layout: Inline
LabelHelper text
Layout: Fluid
Label

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.

Enabled
Label
Hover
Label
Focus
Label
Disabled
Label
Error
LabelChoose an option to continue.
Warning
LabelThis option is being retired.
Read-only
Label

API reference

Generated from the contract, versioned with it, and checked by drift-check. This table cannot describe props the component does not have.

Select props
PropTypeDefaultNotes
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.
hideLabelboolean—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.
readOnlyboolean—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 a listbox of options, and the keyboard’s row is its aria-activedescendant. In error it carries aria-invalid; read-only carries aria-readonly and 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 with aria-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 stays danger.
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 axes and how they map to code
Kit axisValuesCodeHow it maps
SizeSmall · Medium · Largesizesm, md and lg. One to one.
StyleDefault · InlinelayoutBoth. Inline’s trigger has no fill or edge at rest and hugs its value, with the label and message beside it.
StateEnabled · Disabled · Error · WarningstateError 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.
StateRead-onlyreadOnlyThe trigger stays focusable with aria-readonly and refuses to open or change. No fill, a plain outline rule.
StateFocus · Hover—Pseudo-classes (governance rule 7). Hover is :hover on the trigger and steps the fill to elevation-02. Focus is :focus-within.
OpenFalse · True—Runtime state, not a prop: the trigger opens Dropdown’s list, which is how the kit draws Open.
StateSkeleton—No counterpart by rule.
SetSelect - Fluidlayout="fluid"A 64px box, the label inside, the value row at 18, a plain outline rule.
Trigger valueEmpty 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.