Askr
UI & Components

Select

Use select with the current published Askr packages.

  • @askrjs/ui/select0.0.13
  • @askrjs/themes/select0.0.13

How to use select

Select should use the standard themed surface unless the application is deliberately implementing its own design system on top of @askrjs/ui.

  1. Import the themed component from @askrjs/themes/components; use @askrjs/ui directly only when building a custom visual system.
  2. Keep select labels, state, and application actions visible at the call site instead of styling internal DOM nodes.
  3. Verify keyboard behavior, focus movement or return, disabled state, and both standard themes.
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@askrjs/themes/components';

<Select value={status()} onValueChange={status.set}>
  <SelectTrigger aria-label="Project status"><SelectValue /></SelectTrigger>
  <SelectContent>
    <SelectItem value="active">Active</SelectItem>
    <SelectItem value="paused">Paused</SelectItem>
  </SelectContent>
</Select>

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 demos on this page compose `Select`, `SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`, and friends the same way you will in application code — nothing in the examples reaches into internals. Copy a demo 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`, `data-disabled`, `data-side`, and `data-align` attributes rather than baking classes into the DOM structure, so you can restyle a specific state (open, disabled, a given popover side) 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.

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.