Select
Select: anatomy, keyboard behavior, state, and theming in Askr.
Example
import { state } from '@askrjs/askr';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@askrjs/themes/components';
function ProjectStatusSelect() {
const [status, setStatus] = state('active');
return <Select value={status()} onValueChange={setStatus}>
<SelectTrigger aria-label="Project status"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="paused">Paused</SelectItem>
</SelectContent>
</Select>;
}Choose an option with keyboard navigation.
Published props
Generated from the TypeScript declarations shipped by the installed package. Named types in the Type column define the accepted values.
SelectContentAsChildProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
align | align?: OverlayAlign | undefined; | — | — |
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
forceMount | forceMount?: boolean | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
side | side?: OverlaySide | undefined; | — | — |
sideOffset | sideOffset?: number | undefined; | — | — |
SelectContentOwnProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
align | align?: OverlayAlign | undefined; | — | — |
forceMount | forceMount?: boolean | undefined; | — | — |
side | side?: OverlaySide | undefined; | — | — |
sideOffset | sideOffset?: number | undefined; | — | — |
SelectContentProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
align | align?: OverlayAlign | undefined; | — | — |
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
forceMount | forceMount?: boolean | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
side | side?: OverlaySide | undefined; | — | — |
sideOffset | sideOffset?: number | undefined; | — | — |
SelectGroupAsChildProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
SelectGroupProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
SelectItemAsChildProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
textValue | textValue?: string | undefined; | — | — |
type | type?: undefined; | — | — |
value | value: string; | — | — |
SelectItemOwnProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
textValue | textValue?: string | undefined; | — | — |
value | value: string; | — | — |
SelectItemProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
ref | ref?: Ref<HTMLButtonElement>; | — | — |
textValue | textValue?: string | undefined; | — | — |
type | type?: "button" | "submit" | "reset" | undefined; | — | — |
SelectItemTextAsChildProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
SelectItemTextProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLSpanElement>; | — | — |
SelectLabelAsChildProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
SelectLabelProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
SelectOwnProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | Supports literal, nested, array-mapped, and `For`-rendered descendants. |
defaultOpen | defaultOpen?: boolean | undefined; | — | — |
defaultValue | defaultValue?: string | undefined; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
id | id?: string | undefined; | — | — |
name | name?: string | undefined; | — | — |
onOpenChange | onOpenChange?: ((open: boolean) => void) | undefined; | — | — |
onValueChange | onValueChange?: ((value: string) => void) | undefined; | — | — |
open | open?: boolean | undefined; | — | — |
value | value?: string | undefined; | — | — |
SelectPortalProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
SelectProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | Supports literal, nested, array-mapped, and `For`-rendered descendants. |
defaultOpen | defaultOpen?: boolean | undefined; | — | — |
defaultValue | defaultValue?: string | undefined; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
id | id?: string | undefined; | — | — |
name | name?: string | undefined; | — | — |
onOpenChange | onOpenChange?: ((open: boolean) => void) | undefined; | — | — |
onValueChange | onValueChange?: ((value: string) => void) | undefined; | — | — |
open | open?: boolean | undefined; | — | — |
value | value?: string | undefined; | — | — |
SelectSeparatorAsChildProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
SelectSeparatorProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
SelectTriggerAsChildProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
onPress | onPress?: ((event: PressEvent) => void) | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
size | size?: SelectTriggerSize | undefined; | — | — |
type | type?: undefined; | — | — |
SelectTriggerOwnProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
size | size?: SelectTriggerSize | undefined; | — | — |
SelectTriggerProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
onPress | onPress?: ((event: PressEvent) => void) | undefined; | — | — |
ref | ref?: Ref<HTMLButtonElement>; | — | — |
size | size?: SelectTriggerSize | undefined; | — | — |
type | type?: "button" | "submit" | "reset" | undefined; | — | — |
SelectValueAsChildProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
placeholder | placeholder?: string | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
SelectValueOwnProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
placeholder | placeholder?: string | undefined; | — | — |
SelectValueProps
Import from @askrjs/ui/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
placeholder | placeholder?: string | undefined; | — | — |
ref | ref?: Ref<HTMLSpanElement>; | — | — |
Purpose
Select gives you a native-feeling listbox trigger without dictating how it looks. The headless `@askrjs/ui/select` primitive tracks the open state, the selected value, and keyboard navigation; `@askrjs/themes/select` layers on the visual trigger, popover content, and item styling you see in the demos. Reach for it when a user needs to pick exactly one value from a list that's too long or too structured for a radio group.
Install and import
Add both `@askrjs/ui` and `@askrjs/themes` (the headless primitive needs the accompanying visuals to look like anything). Import from the dedicated subpaths — `@askrjs/ui/select` and `@askrjs/themes/select` — rather than the package roots so your bundle only pulls in the select-specific code and its dependency chain.
Live examples
The example composes `Select`, `SelectTrigger`, `SelectValue`, `SelectContent`, and `SelectItem` the same way you will in application code — nothing in it reaches into internals. Copy it wholesale and swap in your own `SelectItem` values as a starting point.
Anatomy
A full select tree is `Select` (state owner) wrapping a `SelectTrigger` with a `SelectValue` inside it, plus a `SelectPortal` containing `SelectContent`. Inside `SelectContent` you place `SelectItem` elements (each wrapping `SelectItemText`), optionally grouped with `SelectGroup`, `SelectLabel`, and separated with `SelectSeparator`. Every part is a real exported component — there's no implicit slot you have to know about.
State model
`Select` owns two independent pieces of state: the selected `value` and the `open` boolean. Each can run uncontrolled — `value`/`defaultValue`/`onValueChange` for the selection, `open`/`defaultOpen`/`onOpenChange` for the popover — or be handed to you fully controlled when the caller needs to own it. Leave both uncontrolled for a typical form field, or control `value` when the selection needs to sync with data loaded elsewhere. Internally the root also tracks a `currentIndexCandidate` for keyboard highlighting, but that's not something you set from the outside.
Keyboard and accessibility
The trigger carries `role="button"` with `aria-expanded`, `aria-controls`, and `aria-haspopup` wired automatically; the popover content gets `role="listbox"` and each item `role="option"` with `aria-selected` reflecting the current value. These roles come straight from the `SELECT_A11Y_CONTRACT` object the package exports, so you can inspect it directly if you want to assert on the contract in tests rather than guessing at attribute names.
Styling and tokens
`@askrjs/themes/select` styles every part through `data-slot`, `data-state`, and `data-disabled` attributes rather than baking classes into the DOM structure — `SelectValue` additionally carries `data-placeholder` when nothing is selected yet, and `SelectTrigger` carries `data-size` for its size variants (there's no `data-side`/`data-align` pair in the real CSS; positioning isn't exposed as a styling hook that way). Restyle a specific state (open, disabled, empty) with an attribute selector instead of overriding a component prop. `SelectTrigger` also exposes a `size` prop (`sm` | `md` | `lg`) for the common case of just needing a bigger or smaller control — `md` is the default and has no dedicated CSS override, since it's what the unstyled base rule already renders.
API
`Select` accepts `value`, `defaultValue`, `onValueChange`, `open`, `defaultOpen`, `onOpenChange`, `name`, and `disabled`. `SelectItem` requires a `value` string and optionally takes `disabled` and `textValue` (useful when the rendered children aren't plain text but you still need a string for matching). `SelectContent` takes `side`, `align`, `sideOffset`, and `forceMount` for positioning and mount-timing control, matching the same overlay-placement props you'll see on popovers and menus elsewhere in the library.
Edge cases
When `name` is set, `Select` renders a hidden `<input type="hidden">` that mirrors the current value, so it participates in native form submission and `FormData` without you wiring anything up. `disabled` on the root disables the whole control; per-item `disabled` skips just that option during keyboard traversal while leaving the rest of the list navigable.
Related pages
If you only need a handful of mutually exclusive options always visible on screen, `Radio Group` is usually a better fit than `Select`. For the browser-native alternative — useful inside constrained contexts like `<form>` elements targeting non-JS fallback — see the Native Select page under experimental controls.