Select
A custom dropdown for picking one value from a list, from @clerk/headless. It is a
headless primitive: it supplies value and open state, portalling, floating-ui
positioning (with optional native-<select>-style item alignment), keyboard navigation
with typeahead, and ARIA wiring, but ships no styles — you bring your own CSS by
targeting the data-* attributes each part emits.
Example
The demo below is intentionally unstyled — it renders the raw primitive so you can see its behavior and ARIA wiring. Open it and pick an option (arrow keys, typeahead, and Enter all work); the trigger reflects the selected value.
Usage
import { Select } from '@clerk/headless/select';
<Select.Root>
<Select.Trigger>
<Select.Value placeholder='Choose a fruit…' />
</Select.Trigger>
<Select.Portal>
<Select.Positioner>
<Select.Popup>
<Select.Option value='apple'>Apple</Select.Option>
<Select.Option value='banana'>Banana</Select.Option>
<Select.Option
value='cherry'
disabled
>
Cherry
</Select.Option>
</Select.Popup>
</Select.Positioner>
</Select.Portal>
</Select.Root>;Controlled
const [value, setValue] = useState('apple');
<Select.Root
value={value}
onValueChange={setValue}
>
{/* trigger + portal */}
</Select.Root>;Parts
| Part | Default Element | Description |
|---|---|---|
Select.Root | none (context) | Owns value and open state plus positioning |
Select.Trigger | <button> | Toggles the dropdown; acts as the positioning anchor |
Select.Value | <span> | Displays the selected option's label, or the placeholder |
Select.Portal | none (portal) | Portals its children; renders nothing until mounted |
Select.Positioner | <div> | Floating-positioned container (role="listbox") |
Select.Popup | <div> | Visual wrapper for the option list |
Select.Option | <button> | A selectable option (role="option") |
Select.Arrow | <svg> | Optional arrow (only positioned when alignItemWithTrigger={false}) |
All rendered parts accept a render prop for polymorphic rendering and standard HTML
attributes for their default element. Select.Arrow accepts floating-ui FloatingArrow
props. Select.Portal is optional.
Props
Select.Root
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Controlled selected value |
defaultValue | string | — | Initial selected value (uncontrolled) |
onValueChange | (value: string) => void | — | Called when the selection changes |
open | boolean | — | Controlled open state |
defaultOpen | boolean | false | Initial open state (uncontrolled) |
onOpenChange | (open: boolean) => void | — | Called when the open state changes |
items | SelectItem[] | — | { label, value }[] used to resolve the selected label early |
alignItemWithTrigger | boolean | true | Overlay the selected item on the trigger (native-<select> style) |
placement | Placement | 'bottom-start' | Placement when not aligning to the item |
sideOffset | number | 4 | Gap in px (applies only when alignItemWithTrigger={false}) |
SelectItem is { label: string; value: string } — disabling is done per-Option.
Select.Value
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder | ReactNode | — | Rendered when no value is selected |
Select.Option
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — (required) | The option's value |
label | string | falls back to value | Display label, also used for typeahead |
disabled | boolean | — | Prevent selection (stays keyboard-focusable) |
Select.Trigger, Select.Positioner, and Select.Popup take no additional props beyond
standard HTML attributes for their default element. Select.Portal accepts root.
Styling
Each part emits data-* attributes you can target with any CSS solution:
| Attribute | Applies To | Description |
|---|---|---|
data-open | Trigger, Popup | Present when the dropdown is open |
data-closed | Trigger, Popup | Present when closed (Popup stays mounted to exit) |
data-selected | Option | Present on the currently selected option |
data-active | Option | Present on the keyboard-highlighted option |
data-disabled | Option | Present on a disabled option |
data-side | Positioner, Arrow | Resolved side: top / bottom / left / right |
data-starting-style | Popup | Present on the entering frame |
data-ending-style | Popup | Present during the exit animation |
The positioner exposes anchor/viewport geometry as CSS variables (and sets an inline
max-height to the available viewport height):
| Variable | Description |
|---|---|
--cl-anchor-width | Trigger width |
--cl-anchor-height | Trigger height |
--cl-available-width | Available width between the anchor and viewport edge |
--cl-available-height | Available height between the anchor and viewport edge |
--cl-transform-origin | transform-origin pointing back toward the anchor |