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/themes/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/themes/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/themes/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/themes/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
SelectGroupProps
Import from @askrjs/themes/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
SelectItemAsChildProps
Import from @askrjs/themes/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/themes/select.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
textValue | textValue?: string | undefined; | — | — |
value | value: string; | — | — |
SelectItemProps
Import from @askrjs/themes/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/themes/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
SelectItemTextProps
Import from @askrjs/themes/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLSpanElement>; | — | — |
SelectLabelAsChildProps
Import from @askrjs/themes/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
SelectLabelProps
Import from @askrjs/themes/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
SelectOwnProps
Import from @askrjs/themes/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/themes/select.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
SelectProps
Import from @askrjs/themes/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/themes/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
SelectSeparatorProps
Import from @askrjs/themes/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
SelectTriggerAsChildProps
Import from @askrjs/themes/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/themes/select.
| Prop | Type | Default | Description |
|---|---|---|---|
size | size?: SelectTriggerSize | undefined; | — | — |
SelectTriggerProps
Import from @askrjs/themes/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/themes/select.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
placeholder | placeholder?: string | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
SelectValueOwnProps
Import from @askrjs/themes/select.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
placeholder | placeholder?: string | undefined; | — | — |
SelectValueProps
Import from @askrjs/themes/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
Verify the trigger popup and expanded attributes, content listbox, option roles and aria-selected relationships through the rendered Select. Exercise keyboard navigation, selection and focus restoration, including disabled and dynamically removed options.
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.