Components
A styled native select element — the browser renders the dropdown, so it stays fast and mobile-friendly.
Playground
NativeSelect — each NativeSelectOption renders a real <option>, and the browser draws the dropdown itself. Use it over Select when you want native behavior, better performance, or mobile-optimized pickers; reach for Select when you need custom styling inside the list, animations, or complex interactions.import {
NativeSelect,
NativeSelectOptGroup,
NativeSelectOption,
} from "@workspace/ui/components/native-select"
<NativeSelect defaultValue="">
<NativeSelectOption value="" disabled>
Select a fruit
</NativeSelectOption>
<NativeSelectOption value="apple">Apple</NativeSelectOption>
<NativeSelectOption value="banana">Banana</NativeSelectOption>
<NativeSelectOption value="blueberry">Blueberry</NativeSelectOption>
<NativeSelectOption value="grapes">Grapes</NativeSelectOption>
<NativeSelectOption value="pineapple">Pineapple</NativeSelectOption>
</NativeSelect>NativeSelectOptGroup — it renders a native <optgroup>, so the browser indents the options and shows the required label as a non-selectable heading.<NativeSelect defaultValue="">
<NativeSelectOption value="" disabled>
Select a department
</NativeSelectOption>
<NativeSelectOptGroup label="Engineering">
<NativeSelectOption value="frontend">Frontend</NativeSelectOption>
<NativeSelectOption value="backend">Backend</NativeSelectOption>
</NativeSelectOptGroup>
<NativeSelectOptGroup label="Sales">
<NativeSelectOption value="account-exec">
Account Executive
</NativeSelectOption>
<NativeSelectOption value="sales-dev">
Sales Development
</NativeSelectOption>
</NativeSelectOptGroup>
<NativeSelectOptGroup label="Operations">
<NativeSelectOption value="finance">Finance</NativeSelectOption>
<NativeSelectOption value="people">People</NativeSelectOption>
</NativeSelectOptGroup>
</NativeSelect>default (36px) and sm (32px). Set size on NativeSelect; the sm size matches sm inputs and buttons in dense layouts.<NativeSelect size="sm" defaultValue="apple">
<NativeSelectOption value="apple">Apple</NativeSelectOption>
{/* … */}
</NativeSelect>
<NativeSelect defaultValue="apple">
<NativeSelectOption value="apple">Apple</NativeSelectOption>
{/* … */}
</NativeSelect>Field, FieldLabel and FieldDescription in forms. Props pass through to the underlying <select>, so point the label's htmlFor at the component's id as usual.Used for emails and in-app copy.
<Field>
<FieldLabel htmlFor="language">Language</FieldLabel>
<NativeSelect id="language" defaultValue="en" className="w-full">
<NativeSelectOption value="en">English</NativeSelectOption>
<NativeSelectOption value="pt">Português</NativeSelectOption>
<NativeSelectOption value="es">Español</NativeSelectOption>
</NativeSelect>
<FieldDescription>
Used for emails and in-app copy.
</FieldDescription>
</Field><select>, so control it the React way — defaultValue for uncontrolled usage, or value and onChange reading event.target.value.Active theme: System
const [value, setValue] = React.useState("system")
<NativeSelect
value={value}
onChange={(event) => setValue(event.target.value)}
className="w-44"
>
<NativeSelectOption value="light">Light</NativeSelectOption>
<NativeSelectOption value="dark">Dark</NativeSelectOption>
<NativeSelectOption value="system">System</NativeSelectOption>
</NativeSelect>disabled on NativeSelect to disable the whole control — the wrapper dims to 50% opacity — or on a single NativeSelectOption to make just that option unselectable.<NativeSelect disabled defaultValue="">
<NativeSelectOption value="">Unavailable</NativeSelectOption>
</NativeSelect>
<NativeSelect defaultValue="">
<NativeSelectOption value="" disabled>
Shipping speed
</NativeSelectOption>
<NativeSelectOption value="standard">Standard</NativeSelectOption>
<NativeSelectOption value="express" disabled>
Express (sold out)
</NativeSelectOption>
</NativeSelect>aria-invalid on NativeSelect to show the invalid styles — a destructive border and ring on the control. Inside a Field, add data-invalid to the wrapper so the label turns destructive too.A country is required.
<Field data-invalid>
<FieldLabel htmlFor="country">Country</FieldLabel>
<NativeSelect id="country" aria-invalid defaultValue="" className="w-full">
<NativeSelectOption value="" disabled>
Select a country
</NativeSelectOption>
<NativeSelectOption value="br">Brazil</NativeSelectOption>
<NativeSelectOption value="us">United States</NativeSelectOption>
<NativeSelectOption value="jp">Japan</NativeSelectOption>
</NativeSelect>
<FieldDescription>A country is required.</FieldDescription>
</Field>dir="rtl" — it reaches the native <select> directly, and the component's logical properties keep the caret and padding mirrored. No provider needed: the browser handles text direction inside the dropdown itself.<div dir="rtl">
<NativeSelect dir="rtl" defaultValue="">
<NativeSelectOption value="" disabled>
اختر فاكهة
</NativeSelectOption>
<NativeSelectOption value="apple">تفاح</NativeSelectOption>
<NativeSelectOption value="banana">موز</NativeSelectOption>
<NativeSelectOption value="grapes">عنب</NativeSelectOption>
</NativeSelect>
</div><select> element inside a wrapper that positions the caret icon, so every native select prop — value, onChange, name, required, form — passes straight through. It ships no JavaScript of its own and stays server-compatible. Options are painted by the browser, styled with the Canvas / CanvasText system colors so the list stays readable in dark mode.| Component | Prop | Default |
|---|---|---|
| NativeSelect | size?: "sm" | "default" | "default" |
| NativeSelect | value? · defaultValue?: string | — |
| NativeSelect | onChange?: (event: ChangeEvent) => void | — |
| NativeSelect | disabled?: boolean | false |
| NativeSelect | required?: boolean | false |
| NativeSelect | name?: string | — |
| NativeSelect | dir?: "ltr" | "rtl" | — |
| NativeSelectOption | value?: string | — |
| NativeSelectOption | disabled?: boolean | false |
| NativeSelectOptGroup | label: string | required |
| NativeSelectOptGroup | disabled?: boolean | false |
Tokens used
7 design tokens consumed by the Native Select component.
--input
Input
--ring
Ring
--destructive
Destructive
--primary
Primary
--primary-foreground
Primary Foreground
--muted-foreground
Muted Foreground
--spacing
Spacing unit (4px)