# Select

> Select: anatomy, keyboard behavior, state, and theming in Askr.

Source: [https://askrjs.com/docs/components/select](https://askrjs.com/docs/components/select)

Status: stable. Packages: @askrjs/ui/select, @askrjs/themes/select.

**Published packages are authoritative.** Examples may lag behind a published contract. When guidance differs, verify the exports and TypeScript declarations in your installed package, then file an issue.

## Example

```tsx
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>;
}
```

## Published props

Generated from the TypeScript declarations shipped by the installed package.

### `SelectContentAsChildProps`

Import from `@askrjs/ui/select`.

- `align?: OverlayAlign | undefined;`
- `asChild: true;`
- `children: JSXElement;`
- `forceMount?: boolean | undefined;`
- `ref?: Ref<Element>;`
- `side?: OverlaySide | undefined;`
- `sideOffset?: number | undefined;`

### `SelectContentOwnProps`

Import from `@askrjs/ui/select`.

- `align?: OverlayAlign | undefined;`
- `forceMount?: boolean | undefined;`
- `side?: OverlaySide | undefined;`
- `sideOffset?: number | undefined;`

### `SelectContentProps`

Import from `@askrjs/ui/select`.

- `align?: OverlayAlign | undefined;`
- `asChild?: false | undefined;`
- `children?: unknown;`
- `forceMount?: boolean | undefined;`
- `ref?: Ref<HTMLDivElement>;`
- `side?: OverlaySide | undefined;`
- `sideOffset?: number | undefined;`

### `SelectGroupAsChildProps`

Import from `@askrjs/ui/select`.

- `asChild: true;`
- `children: JSXElement;`
- `ref?: Ref<Element>;`

### `SelectGroupProps`

Import from `@askrjs/ui/select`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `ref?: Ref<HTMLDivElement>;`

### `SelectItemAsChildProps`

Import from `@askrjs/ui/select`.

- `asChild: true;`
- `children: JSXElement;`
- `disabled?: boolean | undefined;`
- `ref?: Ref<Element>;`
- `textValue?: string | undefined;`
- `type?: undefined;`
- `value: string;`

### `SelectItemOwnProps`

Import from `@askrjs/ui/select`.

- `children?: unknown;`
- `disabled?: boolean | undefined;`
- `textValue?: string | undefined;`
- `value: string;`

### `SelectItemProps`

Import from `@askrjs/ui/select`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `disabled?: boolean | undefined;`
- `ref?: Ref<HTMLButtonElement>;`
- `textValue?: string | undefined;`
- `type?: "button" | "submit" | "reset" | undefined;`

### `SelectItemTextAsChildProps`

Import from `@askrjs/ui/select`.

- `asChild: true;`
- `children: JSXElement;`
- `ref?: Ref<Element>;`

### `SelectItemTextProps`

Import from `@askrjs/ui/select`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `ref?: Ref<HTMLSpanElement>;`

### `SelectLabelAsChildProps`

Import from `@askrjs/ui/select`.

- `asChild: true;`
- `children: JSXElement;`
- `ref?: Ref<Element>;`

### `SelectLabelProps`

Import from `@askrjs/ui/select`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `ref?: Ref<HTMLDivElement>;`

### `SelectOwnProps`

Import from `@askrjs/ui/select`.

- `children?: unknown;` — Supports literal, nested, array-mapped, and `For`-rendered descendants.
- `defaultOpen?: boolean | undefined;`
- `defaultValue?: string | undefined;`
- `disabled?: boolean | undefined;`
- `id?: string | undefined;`
- `name?: string | undefined;`
- `onOpenChange?: ((open: boolean) => void) | undefined;`
- `onValueChange?: ((value: string) => void) | undefined;`
- `open?: boolean | undefined;`
- `value?: string | undefined;`

### `SelectPortalProps`

Import from `@askrjs/ui/select`.

- `children?: unknown;`

### `SelectProps`

Import from `@askrjs/ui/select`.

- `children?: unknown;` — Supports literal, nested, array-mapped, and `For`-rendered descendants.
- `defaultOpen?: boolean | undefined;`
- `defaultValue?: string | undefined;`
- `disabled?: boolean | undefined;`
- `id?: string | undefined;`
- `name?: string | undefined;`
- `onOpenChange?: ((open: boolean) => void) | undefined;`
- `onValueChange?: ((value: string) => void) | undefined;`
- `open?: boolean | undefined;`
- `value?: string | undefined;`

### `SelectSeparatorAsChildProps`

Import from `@askrjs/ui/select`.

- `asChild: true;`
- `children: JSXElement;`
- `ref?: Ref<Element>;`

### `SelectSeparatorProps`

Import from `@askrjs/ui/select`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `ref?: Ref<HTMLDivElement>;`

### `SelectTriggerAsChildProps`

Import from `@askrjs/ui/select`.

- `asChild: true;`
- `children: JSXElement;`
- `disabled?: boolean | undefined;`
- `onPress?: ((event: PressEvent) => void) | undefined;`
- `ref?: Ref<Element>;`
- `size?: SelectTriggerSize | undefined;`
- `type?: undefined;`

### `SelectTriggerOwnProps`

Import from `@askrjs/ui/select`.

- `size?: SelectTriggerSize | undefined;`

### `SelectTriggerProps`

Import from `@askrjs/ui/select`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `disabled?: boolean | undefined;`
- `onPress?: ((event: PressEvent) => void) | undefined;`
- `ref?: Ref<HTMLButtonElement>;`
- `size?: SelectTriggerSize | undefined;`
- `type?: "button" | "submit" | "reset" | undefined;`

### `SelectValueAsChildProps`

Import from `@askrjs/ui/select`.

- `asChild: true;`
- `children: JSXElement;`
- `placeholder?: string | undefined;`
- `ref?: Ref<Element>;`

### `SelectValueOwnProps`

Import from `@askrjs/ui/select`.

- `children?: unknown;`
- `placeholder?: string | undefined;`

### `SelectValueProps`

Import from `@askrjs/ui/select`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `placeholder?: string | undefined;`
- `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.

## Documentation navigation

[Previous](https://askrjs.com/docs/components/radio-group/index.md) | [Next](https://askrjs.com/docs/components/slider/index.md)
