Askr documentation
UI & Components

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>;
}
Try 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.

PropTypeDefaultDescription
alignalign?: OverlayAlign | undefined;——
asChildasChild: true;——
childrenchildren: JSXElement;——
forceMountforceMount?: boolean | undefined;——
refref?: Ref<Element>;——
sideside?: OverlaySide | undefined;——
sideOffsetsideOffset?: number | undefined;——

SelectContentOwnProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
alignalign?: OverlayAlign | undefined;——
forceMountforceMount?: boolean | undefined;——
sideside?: OverlaySide | undefined;——
sideOffsetsideOffset?: number | undefined;——

SelectContentProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
alignalign?: OverlayAlign | undefined;——
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
forceMountforceMount?: boolean | undefined;——
refref?: Ref<HTMLDivElement>;——
sideside?: OverlaySide | undefined;——
sideOffsetsideOffset?: number | undefined;——

SelectGroupAsChildProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
asChildasChild: true;——
childrenchildren: JSXElement;——
refref?: Ref<Element>;——

SelectGroupProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
refref?: Ref<HTMLDivElement>;——

SelectItemAsChildProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
asChildasChild: true;——
childrenchildren: JSXElement;——
disableddisabled?: boolean | undefined;——
refref?: Ref<Element>;——
textValuetextValue?: string | undefined;——
typetype?: undefined;——
valuevalue: string;——

SelectItemOwnProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
childrenchildren?: unknown;——
disableddisabled?: boolean | undefined;——
textValuetextValue?: string | undefined;——
valuevalue: string;——

SelectItemProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
disableddisabled?: boolean | undefined;——
refref?: Ref<HTMLButtonElement>;——
textValuetextValue?: string | undefined;——
typetype?: "button" | "submit" | "reset" | undefined;——

SelectItemTextAsChildProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
asChildasChild: true;——
childrenchildren: JSXElement;——
refref?: Ref<Element>;——

SelectItemTextProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
refref?: Ref<HTMLSpanElement>;——

SelectLabelAsChildProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
asChildasChild: true;——
childrenchildren: JSXElement;——
refref?: Ref<Element>;——

SelectLabelProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
refref?: Ref<HTMLDivElement>;——

SelectOwnProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
childrenchildren?: unknown;—Supports literal, nested, array-mapped, and `For`-rendered descendants.
defaultOpendefaultOpen?: boolean | undefined;——
defaultValuedefaultValue?: string | undefined;——
disableddisabled?: boolean | undefined;——
idid?: string | undefined;——
namename?: string | undefined;——
onOpenChangeonOpenChange?: ((open: boolean) => void) | undefined;——
onValueChangeonValueChange?: ((value: string) => void) | undefined;——
openopen?: boolean | undefined;——
valuevalue?: string | undefined;——

SelectPortalProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
childrenchildren?: unknown;——

SelectProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
childrenchildren?: unknown;—Supports literal, nested, array-mapped, and `For`-rendered descendants.
defaultOpendefaultOpen?: boolean | undefined;——
defaultValuedefaultValue?: string | undefined;——
disableddisabled?: boolean | undefined;——
idid?: string | undefined;——
namename?: string | undefined;——
onOpenChangeonOpenChange?: ((open: boolean) => void) | undefined;——
onValueChangeonValueChange?: ((value: string) => void) | undefined;——
openopen?: boolean | undefined;——
valuevalue?: string | undefined;——

SelectSeparatorAsChildProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
asChildasChild: true;——
childrenchildren: JSXElement;——
refref?: Ref<Element>;——

SelectSeparatorProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
refref?: Ref<HTMLDivElement>;——

SelectTriggerAsChildProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
asChildasChild: true;——
childrenchildren: JSXElement;——
disableddisabled?: boolean | undefined;——
onPressonPress?: ((event: PressEvent) => void) | undefined;——
refref?: Ref<Element>;——
sizesize?: SelectTriggerSize | undefined;——
typetype?: undefined;——

SelectTriggerOwnProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
sizesize?: SelectTriggerSize | undefined;——

SelectTriggerProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
disableddisabled?: boolean | undefined;——
onPressonPress?: ((event: PressEvent) => void) | undefined;——
refref?: Ref<HTMLButtonElement>;——
sizesize?: SelectTriggerSize | undefined;——
typetype?: "button" | "submit" | "reset" | undefined;——

SelectValueAsChildProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
asChildasChild: true;——
childrenchildren: JSXElement;——
placeholderplaceholder?: string | undefined;——
refref?: Ref<Element>;——

SelectValueOwnProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
childrenchildren?: unknown;——
placeholderplaceholder?: string | undefined;——

SelectValueProps

Import from @askrjs/themes/select.

PropTypeDefaultDescription
asChildasChild?: false | undefined;——
childrenchildren?: unknown;——
placeholderplaceholder?: string | undefined;——
refref?: 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.

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.