Select
StableA styled native dropdown with a drawn chevron, a placeholder and an invalid state.
Also known as dropdown, dropdown list, picker, native select
Install with npm install @tattva/ui (setup), or copy the code from the kit in Downloads.
Options from data
function Example() {
const [v, setV] = useState("balanced");
return (
<div className="w-60">
<Select aria-label="Model" value={v} onChange={(e) => setV(e.target.value)} options={models} />
</div>
);
}Pass options. An option can be disabled. Pass an aria-label when there is no visible label.
When to use it
Picks one value from a list that is too long for side-by-side buttons, using the platform's own menu so it is reliable everywhere.
Made of: Box · Selected text · Chevron
Use it for
- Choosing a model, language or time zone from a list.
- Any single choice from more than about five options.
- A form where the native picker on phones is an advantage.
Not for
- Two to four options that should all stay visible: use SegmentedControl.
- A menu with actions, icons or rich content: use Popover.
- Free text the user types: use TextField.
Do and avoid
Do
- Use it for a list the user chooses from once.
- Put the most likely choice first, or set a value up front.
- Wrap it in Field so it has a visible label.
- Use children with optgroup to group long lists.
Avoid
- Do not use it for two or three options. Use SegmentedControl.
- Do not put actions in it, such as Delete. Use a menu.
- Do not use placeholder as the label.
- Do not try to style the open menu. It belongs to the browser.
On a phone
- It is a native select, so a phone shows its own picker and the open list is not styled by this system.
- It fills the width of its container, is 44px tall on touch screens, and its text is 16px so iPhones do not zoom.
Properties
| Prop | Type | Default | Description |
|---|---|---|---|
| options | { value: string; label: string; disabled?: boolean }[] | none | The choices. Use this or children. If both are given, options come first. |
| children | ReactNode | none | <option> and <optgroup> elements, for cases options cannot express. |
| placeholder | string | none | A hidden, disabled first option shown while the value is empty. It shows in a muted colour. |
| invalid | boolean | none | Danger border and aria-invalid. |
| size | "sm" | "md" | "md" | md is 40px tall, sm is 32px. Both are 44px on coarse pointers. |
| className | string | none | Classes for the wrapper around the select. |
| ref | Ref<HTMLSelectElement> | none | Reaches the native select. A normal prop in React 19. |
| ...rest | Omit<SelectHTMLAttributes<HTMLSelectElement>, "size"> | none | Everything else goes to the <select>: value, defaultValue, onChange, disabled, required, id, aria-label, aria-describedby and so on. |
"Show code under the preview gives the code for the example on the stage."
In Figma
In Figma, one Select switches all of this from its properties panel, across 20 variants. Each property sets the React prop beside it, so design and code use the same names.
| Figma property | React | Values |
|---|---|---|
| Invalid | invalid | On or off |
| Size | size | SM, MD |
| State | Hover, Pressed and Focus come from the browser. Disabled sets disabled. | Default, Hover, Pressed, Focus, Disabled |
States
States this part has beyond its default look. Build each one your screen can reach. States set by a property are in the Code tab.
- hover
- The border gets stronger when the pointer is over the select.
- focus
- The global focus ring shows around the select when you reach it with the keyboard.
- disabled
- Pass disabled. The select is dimmed and skipped by the Tab key.
- invalid
- Pass invalid for a danger border and aria-invalid.
- placeholder
- While the value is empty and placeholder is set, the placeholder shows in a muted colour.
Tokens
Change one of these and every part that uses it follows.
--border--border-strong--danger--surface--fg--fg-muted--fg-subtle--dur-fast--radius-field
Built in
- It is a native select, so the keyboard, type-ahead and screen reader behaviour come from the browser.
- The chevron is hidden from screen readers and does not catch clicks.
- invalid sets aria-invalid.
- The focus ring is the global one and shows for keyboard focus.
- A disabled select is skipped by the Tab key.
- The select is 44px tall on coarse pointers, and the browser's own picker opens on touch.
What you need to do
- Give it a name: wrap it in Field, or pass aria-label.
- Use a placeholder only as a prompt. Do not rely on it to say what the field is.
- Keep option labels short and unique.
Built toward WCAG 2.2 AA and checked automatically; no independent audit yet. Read the accessibility guide