

# Select

Dropdown single-select. `Select` with `options` is the assembled preset (`label`, `description`, `errorMessage`, `placeholder`). Compose `Select.Root`, `Select.Trigger`, `Select.Value`, `Select.Popover`, `Select.ListBox`, and `Select.Item` when a row needs more than a label.

Custom item rows are React-only. Astro and HTML previews keep using a native `<select>` with the core recipe classes.

## Examples

### Default

```tsx
import { Select } from '@var-ui/react';

<Select
  label="Fruit"
  options={[
    { id: 'apple', label: 'Apple' },
    { id: 'plum', label: 'Plum' },
  ]}
/>
```

```astro
---
// No @var-ui/astro Select — core recipe + native <select>.
// React uses options={{ id, label }}; HTML uses <option value>.
import { select } from '@var-ui/core';

const s = select();
const options = [
  { id: 'apple', label: 'Apple' },
  { id: 'plum', label: 'Plum' },
];
---

<div class:list={[s.root]}>
  <label class:list={[s.label]} for="fruit">Fruit</label>
  <select class:list={[s.trigger]} id="fruit">
    <option value="" disabled selected>Select…</option>
    {options.map((option) => <option value={option.id}>{option.label}</option>)}
  </select>
</div>
```

```html
<div class="var-ui-select"><label class="var-ui-select__label" for="fruit">Fruit</label><select class="var-ui-select__trigger" id="fruit"><option value="" disabled selected>Select…</option><option value="apple">Apple</option><option value="plum">Plum</option></select></div>
```

### Options list

Pass an `options` array of `{ id, label }` objects. Use `placeholder` when no value is selected.

```tsx
import { Grid, Select } from '@var-ui/react';

const options = [
  { id: 'apple', label: 'Apple' },
  { id: 'plum', label: 'Plum' },
  { id: 'orange', label: 'Orange' },
  { id: 'grape', label: 'Grape' },
];

<Grid columns={2} gap="lg">
  <Select label="Fruit" options={options} />
  <Select label="Favorite fruit" placeholder="Choose a fruit…" options={options} />
</Grid>
```

```astro
---
// No @var-ui/astro Select — core grid + select recipes with native <select>.
import { grid, select } from '@var-ui/core';

const g = grid({ columns: 'two', gap: 'lg' });
const s = select();
const options = [
  { id: 'apple', label: 'Apple' },
  { id: 'plum', label: 'Plum' },
  { id: 'orange', label: 'Orange' },
  { id: 'grape', label: 'Grape' },
];
---

<div class:list={[g]}>
  <div class:list={[s.root]}>
    <label class:list={[s.label]} for="fruit-options">Fruit</label>
    <select class:list={[s.trigger]} id="fruit-options">
      <option value="" disabled selected>Select…</option>
      {options.map((option) => <option value={option.id}>{option.label}</option>)}
    </select>
  </div>
  <div class:list={[s.root]}>
    <label class:list={[s.label]} for="favorite-fruit">Favorite fruit</label>
    <select class:list={[s.trigger]} id="favorite-fruit">
      <option value="" disabled selected>Choose a fruit…</option>
      {options.map((option) => <option value={option.id}>{option.label}</option>)}
    </select>
  </div>
</div>
```

```html
<div data-columns="two" data-gap="lg" class="var-ui-grid"><div class="var-ui-select"><label class="var-ui-select__label" for="fruit-options">Fruit</label><select class="var-ui-select__trigger" id="fruit-options"><option value="" disabled selected>Select…</option><option value="apple">Apple</option><option value="plum">Plum</option><option value="orange">Orange</option><option value="grape">Grape</option></select></div><div class="var-ui-select"><label class="var-ui-select__label" for="favorite-fruit">Favorite fruit</label><select class="var-ui-select__trigger" id="favorite-fruit"><option value="" disabled selected>Choose a fruit…</option><option value="apple">Apple</option><option value="plum">Plum</option><option value="orange">Orange</option><option value="grape">Grape</option></select></div></div>
```

### Compound parts

Compose parts for custom item content. `textValue` is the string used for typeahead and the default closed trigger. Compose `Select.Value` inside `Select.Trigger` when the closed value should match custom row chrome. Pass `portalContainer` on `Select.Popover` when the list must portal into a themed subtree instead of `document.body`.

```tsx
import { Avatar, HStack, Select } from '@var-ui/react';

const PEOPLE = [
  { id: 'ada', name: 'Ada Lovelace' },
  { id: 'grace', name: 'Grace Hopper' },
];

<Select.Root>
  <Select.Label>Assignee</Select.Label>
  <Select.Trigger>
    <Select.Value>
      {({ selectedText, isPlaceholder }) =>
        isPlaceholder ? (
          'Select a person…'
        ) : (
          <HStack gap="sm">
            <Avatar name={selectedText} size="sm" />
            {selectedText}
          </HStack>
        )
      }
    </Select.Value>
  </Select.Trigger>
  <Select.Popover>
    <Select.ListBox>
      {PEOPLE.map((person) => (
        <Select.Item key={person.id} id={person.id} textValue={person.name}>
          <HStack gap="sm">
            <Avatar name={person.name} size="sm" />
            {person.name}
          </HStack>
        </Select.Item>
      ))}
    </Select.ListBox>
  </Select.Popover>
</Select.Root>
```

```astro
<!-- Custom item rows are React-only — use @var-ui/react -->
```

```html
<!-- Custom item rows are React-only — use @var-ui/react -->
```

## Props

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| options | SelectOption[] | yes |  | Options shown in the dropdown listbox. |
| className | string \| undefined | no |  | Additional CSS class names merged onto the root. |
| description | string \| undefined | no |  | Helper text shown below the control when there is no error. |
| errorMessage | string \| undefined | no |  | Validation message; when set, the field is shown in an error state. |
| label | string \| undefined | no |  | Visible label rendered above the control. |
| placeholder | string \| undefined | no | 'Select an item' (localized) | Temporary text that occupies the select when it is empty. Placeholder text when no value is selected. |
| portalContainer | Element \| undefined | no |  | Element the dropdown listbox portals into instead of `document.body`. Needed when a subtree renders under a different theme than the page ambient (the theme's CSS custom properties only cascade to descendants of the themed element). |
| aria-describedby | string \| undefined | no |  | Identifies the element (or elements) that describes the object. |
| aria-details | string \| undefined | no |  | Identifies the element (or elements) that provide a detailed, extended description for the object. |
| aria-label | string \| undefined | no |  | Defines a string value that labels the current element. |
| aria-labelledby | string \| undefined | no |  | Identifies the element (or elements) that labels the current element. |
| autoComplete | string \| undefined | no |  | Describes the type of autocomplete functionality the input should provide if any. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefautocomplete). |
| autoFocus | boolean \| undefined | no |  | Whether the element should receive focus on render. |
| defaultValue | Key \| null \| undefined | no |  | The default value (uncontrolled). |
| dir | string \| undefined | no |  |  |
| disabledKeys | Iterable<Key> \| undefined | no |  | The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with. |
| excludeFromTabOrder | boolean \| undefined | no |  | Whether to exclude the element from the sequential tab order. If true, the element will not be focusable via the keyboard by tabbing. This should be avoided except in rare scenarios where an alternative means of accessing the element or its functionality via the keyboard is available. |
| form | string \| undefined | no |  | The `<form>` element to associate the input with. The value of this attribute must be the id of a `<form>` in the same document. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#form). |
| hidden | boolean \| undefined | no |  |  |
| id | string \| undefined | no |  | The element's unique identifier. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id). |
| inert | boolean \| undefined | no |  |  |
| isDisabled | boolean \| undefined | no |  | Whether the input is disabled. |
| isInvalid | boolean \| undefined | no |  | Whether the input value is invalid. |
| isRequired | boolean \| undefined | no |  | Whether user input is required on the input before form submission. |
| lang | string \| undefined | no |  |  |
| name | string \| undefined | no |  | The name of the input, used when submitting an HTML form. |
| onAnimationEnd | AnimationEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onAnimationEndCapture | AnimationEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onAnimationIteration | AnimationEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onAnimationIterationCapture | AnimationEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onAnimationStart | AnimationEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onAnimationStartCapture | AnimationEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onAuxClick | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onAuxClickCapture | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onBlur | ((e: FocusEvent<Element, Element>) => void) \| undefined | no |  | Handler that is called when the element loses focus. |
| onChange | ((value: Key \| null) => void) \| undefined | no |  | Handler that is called when the value changes. |
| onClick | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onClickCapture | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onContextMenu | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onContextMenuCapture | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onDoubleClick | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onDoubleClickCapture | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onFocus | ((e: FocusEvent<Element, Element>) => void) \| undefined | no |  | Handler that is called when the element receives focus. |
| onFocusChange | ((isFocused: boolean) => void) \| undefined | no |  | Handler that is called when the element's focus status changes. |
| onGotPointerCapture | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onGotPointerCaptureCapture | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onKeyDown | ((e: KeyboardEvent) => void) \| undefined | no |  | Handler that is called when a key is pressed. |
| onKeyUp | ((e: KeyboardEvent) => void) \| undefined | no |  | Handler that is called when a key is released. |
| onLostPointerCapture | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onLostPointerCaptureCapture | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onMouseDown | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onMouseDownCapture | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onMouseEnter | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onMouseLeave | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onMouseMove | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onMouseMoveCapture | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onMouseOut | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onMouseOutCapture | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onMouseOver | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onMouseOverCapture | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onMouseUp | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onMouseUpCapture | MouseEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onPointerCancel | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onPointerCancelCapture | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onPointerDown | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onPointerDownCapture | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onPointerEnter | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onPointerLeave | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onPointerMove | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onPointerMoveCapture | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onPointerOut | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onPointerOutCapture | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onPointerOver | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onPointerOverCapture | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onPointerUp | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onPointerUpCapture | PointerEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onScroll | UIEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onScrollCapture | UIEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onTouchCancel | TouchEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onTouchCancelCapture | TouchEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onTouchEnd | TouchEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onTouchEndCapture | TouchEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onTouchMove | TouchEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onTouchMoveCapture | TouchEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onTouchStart | TouchEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onTouchStartCapture | TouchEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onTransitionCancel | TransitionEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onTransitionCancelCapture | TransitionEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onTransitionEnd | TransitionEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onTransitionEndCapture | TransitionEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onTransitionRun | TransitionEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onTransitionRunCapture | TransitionEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onTransitionStart | TransitionEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onTransitionStartCapture | TransitionEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onWheel | WheelEventHandler<HTMLDivElement> \| undefined | no |  |  |
| onWheelCapture | WheelEventHandler<HTMLDivElement> \| undefined | no |  |  |
| render | DOMRenderFunction<"div", SelectRenderProps> \| undefined | no |  | Overrides the default DOM element with a custom render function. This allows rendering existing components with built-in styles and behaviors such as router links, animation libraries, and pre-styled components.  Requirements:  - You must render the expected element type (e.g. if `<button>` is expected, you cannot render an   `<a>`). - Only a single root DOM element can be rendered (no fragments). - You must pass through props and ref to the underlying DOM element, merging with your own prop   as appropriate. |
| slot | string \| null \| undefined | no |  | A slot name for the component. Slots allow the component to receive props from a parent component. An explicit `null` value indicates that the local props completely override all props received from a parent. |
| style | StyleOrFunction<SelectRenderProps> \| undefined | no |  | The inline [style](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style) for the element. A function may be provided to compute the style based on component state. |
| translate | "yes" \| "no" \| undefined | no |  |  |
| validate | ((value: Key) => true \| ValidationError \| null \| undefined) \| undefined | no |  | A function that returns an error message if a given value is invalid. Validation errors are displayed to the user when the form is submitted if `validationBehavior="native"`. For realtime validation, use the `isInvalid` prop instead. |
| validationBehavior | "native" \| "aria" \| undefined | no | 'native' | Whether to use native HTML form validation to prevent form submission when the value is missing or invalid, or mark the field as required or invalid via ARIA. |
| value | Key \| null \| undefined | no |  | The current value (controlled). |
| allowsEmptyCollection | boolean \| undefined | no |  | Whether the select should be allowed to be open when the collection is empty. |
| defaultOpen | boolean \| undefined | no |  | Sets the default open state of the menu. |
| defaultSelectedKey | Key \| null \| undefined | no |  | The initial selected key in the collection (uncontrolled). |
| isOpen | boolean \| undefined | no |  | Sets the open state of the menu. |
| onOpenChange | ((isOpen: boolean) => void) \| undefined | no |  | Method that is called when the open state of the menu changes. |
| onSelectionChange | ((key: Key \| null) => void) \| undefined | no |  | Handler that is called when the selection changes. |
| selectedKey | Key \| null \| undefined | no |  | The currently selected key in the collection (controlled). |
| selectionMode | "single" \| undefined | no | 'single' | Whether single or multiple selection is enabled. |
| shouldCloseOnSelect | boolean \| undefined | no |  | Whether the Select should close when an item is selected. Defaults to true if selectionMode is single, false otherwise. |

## Accessibility

Built on React Aria Components where applicable.
