Select
- Component overview: Single-selection dropdown with optional trigger-input search filtering. The public API uses a declarative
optionsconfiguration, and there is no arrow icon on the trigger per the Figma spec. - Size baseline: Trigger min-height 40px, horizontal padding 16px, border radius 5px. Menu item padding 16px × 8px.
- Implementation note:
ui/selectretains structural skeleton withunstyledVisual; the design layer owns border, color, shadow, and state visuals for all sub-components. - Figma spec
Basic Usage
Declarative options API — the simplest way to render a select. Set searchable to turn the trigger into a searchable input and filter the dropdown as you type.
render( <div style={{ width: 280 }}> <Select placeholder="Select a member role" options={[ { value: 'owner', label: 'Owner' }, { value: 'admin', label: 'Admin' }, { value: 'editor', label: 'Editor' }, { value: 'viewer', label: 'Viewer' }, { value: 'commenter', label: 'Commenter' }, { value: 'guest', label: 'Guest' }, { value: 'billing', label: 'Billing' }, { value: 'auditor', label: 'Auditor' }, { value: 'developer', label: 'Developer' }, { value: 'support', label: 'Support' }, ]} /> </div>, )
Grouped Options
Use type: 'group' and type: 'separator' for categorized menus.
render( <div style={{ width: 280 }}> <Select placeholder="Select a team" options={[ { type: 'group', label: 'Workspace', options: [ { value: 'design', label: 'Design' }, { value: 'engineering', label: 'Engineering' }, ], }, { type: 'separator' }, { type: 'group', label: 'Status', options: [ { value: 'active', label: 'Active' }, { value: 'pending', label: 'Pending', disabled: true }, ], }, ]} /> </div>, )
States
Trigger states aligned with Figma variant matrix: State × Filled × Status.
render( <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 16, maxWidth: 760, }} > <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Normal / Empty / Default</span> <Select placeholder="Select" options={[ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, ]} /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Normal / Filled / Default</span> <Select defaultValue="banana" placeholder="Select" options={[ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, ]} /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Normal / Empty / Error</span> <Select placeholder="Error state" error options={[ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, ]} /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Normal / Filled / Error</span> <Select defaultValue="banana" error options={[ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, ]} /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Disabled / Empty</span> <Select disabled placeholder="Unavailable" options={[{ value: 'apple', label: 'Apple' }]} /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Disabled / Filled</span> <Select defaultValue="banana" disabled placeholder="Unavailable" options={[ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, ]} /> </div> </div>, )
Searchable
Set searchable to turn the trigger itself into an input. Typing in the trigger filters the dropdown options in real time.
render( <div style={{ width: 280 }}> <Select placeholder="Search and select" searchable options={[ { value: 'alice', label: 'Alice Johnson' }, { value: 'bob', label: 'Bob Smith' }, { value: 'charlie', label: 'Charlie Brown' }, { value: 'diana', label: 'Diana Prince' }, { value: 'eve', label: 'Eve Williams' }, ]} /> </div>, )
Trigger Token Table
| State | Border | Background | Note |
|---|---|---|---|
| Default | Separators/Emphasized#CCCCCC | transparent | Collapsed |
| Focused / Open | Labels/Primary#000000 | transparent | Expanded or focused |
| Error | Status/Destructive#FF503F | transparent | error prop |
| Disabled | Separators/Emphasized#CCCCCC | Grays/Gray-1#EBEBEB | disabled prop |
Menu Token Table
| Element | Property | Token / Value |
|---|---|---|
| Content | Background | Grays/White#FFFFFF |
| Content | Border | Separators/Default#EBEBEB |
| Content | Shadow | Effects/Shadow/Defaultrgba(0,0,0,0.1) |
| Content | Border radius | Radius_5 (5px) |
| Content | Min width | 160px |
| Content | Padding (vertical) | Spacing_8 (8px) |
| Item | Text color | Labels/Secondary#3D3D3D |
| Item | Hover background | Grays/Gray-1#EBEBEB |
| Item (disabled) | Text color | Labels/Disabled#A3A3A3 |
| Label | Text color | Labels/Tertiary#757575 |
| Separator | Color | Separators/Non-opaquergba(0,0,0,0.08) |
Size Spec
| Dimension | Value |
|---|---|
| Trigger min-height | 40px |
| Trigger horizontal padding | 16px |
| Trigger border radius | 5px |
| Trigger font | Body/Regular (14px, line-height 22) |
| Trigger arrow icon | None (hidden per Figma) |
| Content min-width | 160px |
| Content border radius | 5px |
| Content vertical padding | 8px |
| Item horizontal padding | 16px |
| Item vertical padding | 8px |
| Item font | Body/Regular (14px, line-height 22) |
| Separator horizontal margin | 16px |
| Separator vertical margin | 4px |
Props
Select (declarative)
| Prop | Type | Default | Description |
|---|---|---|---|
options | SelectOptionEntry[] | - | Declarative option entries |
placeholder | string | - | Placeholder text |
value | string | - | Controlled value |
defaultValue | string | - | Initial value (uncontrolled) |
searchable | boolean | false | Enable search input in dropdown |
error | boolean | false | Error state on trigger |
disabled | boolean | false | Disabled |
open | boolean | - | Controlled open state |
defaultOpen | boolean | false | Initial open state |
onValueChange | (value: string) => void | - | Selected value change callback |
onOpenChange | (open: boolean) => void | - | Open state change callback |
triggerClassName | string | - | Custom trigger class |
contentClassName | string | - | Custom content class |
aria-label | string | 'select' | Accessibility label |
SelectOptionEntry
// Basic option
{ value: string; label: ReactNode; disabled?: boolean }
// Group
{ type: 'group'; label: ReactNode; options: SelectOptionItem[] }
// Separator
{ type: 'separator' }
MultiSelect (Multi Chips Mode)
For multi-selection scenarios, use MultiSelect. Selected values are displayed as removable chips in the trigger. Includes an inline input for search filtering and optional creation of new options.
render( <div style={{ width: 320 }}> <MultiSelect placeholder="Add email" options={[ { value: 'alice@example.com', label: 'alice@example.com' }, { value: 'bob@example.com', label: 'bob@example.com' }, { value: 'charlie@example.com', label: 'charlie@example.com' }, { value: 'a_very_long_email_address@gmail.com', label: 'a_very_long_email_address@gmail.com', }, ]} defaultValue={['alice@example.com', 'bob@example.com']} /> </div>, )
MultiSelect States
render( <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 16, maxWidth: 760, }} > <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Normal / Empty</span> <MultiSelect placeholder="Add email" options={[ { value: 'a', label: 'alice@example.com' }, { value: 'b', label: 'bob@example.com' }, ]} /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Normal / Filled</span> <MultiSelect placeholder="Add email" options={[ { value: 'a', label: 'alice@example.com' }, { value: 'b', label: 'bob@example.com' }, ]} defaultValue={['a', 'b']} /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Error</span> <MultiSelect placeholder="Add email" error options={[ { value: 'a', label: 'alice@example.com' }, { value: 'b', label: 'bob@example.com' }, ]} defaultValue={['a']} /> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <span style={{ fontSize: 12, color: '#999' }}>Disabled</span> <MultiSelect placeholder="Add email" disabled options={[ { value: 'a', label: 'alice@example.com' }, { value: 'b', label: 'bob@example.com' }, { value: 'c', label: 'my_name_is_too_long_to_read@gmail.com' }, ]} defaultValue={['a', 'b', 'c']} /> </div> </div>, )
Search Filtering
Type in the input to filter options. Backspace on empty input removes the last chip.
render( <div style={{ width: 320 }}> <MultiSelect placeholder="Search and select members" searchable options={[ { value: 'alice', label: 'Alice Johnson' }, { value: 'bob', label: 'Bob Smith' }, { value: 'charlie', label: 'Charlie Brown' }, { value: 'diana', label: 'Diana Prince' }, { value: 'eve', label: 'Eve Williams' }, ]} /> </div>, )
Creatable
Set searchable + creatable to allow creating new options by typing and pressing Enter.
render( <div style={{ width: 320 }}> <MultiSelect placeholder="Add tags (type + Enter)" searchable creatable options={[ { value: 'bug', label: 'Bug' }, { value: 'feature', label: 'Feature' }, { value: 'docs', label: 'Documentation' }, ]} defaultValue={['bug']} /> </div>, )
MultiSelect Props
| Prop | Type | Default | Description |
|---|---|---|---|
options | MultiSelectOption[] | - | Option list (required) |
placeholder | string | - | Placeholder text |
value | string[] | - | Controlled selected values |
defaultValue | string[] | [] | Initial values (uncontrolled) |
onChange | (value: string[]) => void | - | Selection change callback |
searchable | boolean | false | Enable inline search input |
creatable | boolean | false | Allow creating new options via Enter (requires searchable) |
onCreate | (input: string) => MultiSelectOption | - | Custom create callback |
error | boolean | false | Error state |
disabled | boolean | false | Disabled |
triggerClassName | string | - | Custom trigger class |
contentClassName | string | - | Custom content class |
aria-label | string | 'multi-select' | Accessibility label |
MultiSelectOption
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | Option value (required) |
label | ReactNode | - | Display text |
searchText | string | - | Custom search text (when label is not a string) |
disabled | boolean | false | Disabled |