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/ui/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/ui/select.

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

SelectContentProps

Import from @askrjs/ui/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/ui/select.

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

SelectGroupProps

Import from @askrjs/ui/select.

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

SelectItemAsChildProps

Import from @askrjs/ui/select.

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

SelectItemOwnProps

Import from @askrjs/ui/select.

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

SelectItemProps

Import from @askrjs/ui/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/ui/select.

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

SelectItemTextProps

Import from @askrjs/ui/select.

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

SelectLabelAsChildProps

Import from @askrjs/ui/select.

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

SelectLabelProps

Import from @askrjs/ui/select.

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

SelectOwnProps

Import from @askrjs/ui/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/ui/select.

PropTypeDefaultDescription
childrenchildren?: unknown;

SelectProps

Import from @askrjs/ui/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/ui/select.

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

SelectSeparatorProps

Import from @askrjs/ui/select.

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

SelectTriggerAsChildProps

Import from @askrjs/ui/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/ui/select.

PropTypeDefaultDescription
sizesize?: SelectTriggerSize | undefined;

SelectTriggerProps

Import from @askrjs/ui/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/ui/select.

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

SelectValueOwnProps

Import from @askrjs/ui/select.

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

SelectValueProps

Import from @askrjs/ui/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

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.

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.