```tsx
import * as Select from '@gentleduck/primitives/select'
```

## Anatomy

```tsx
<Select.Root>
  <Select.Trigger>
    <Select.Value />
    <Select.Icon />
  </Select.Trigger>
  <Select.Portal>
    <Select.Content>
      <Select.ScrollUpButton />
      <Select.Viewport>
        <Select.Group>
          <Select.Label />
          <Select.Item>
            <Select.ItemText />
            <Select.ItemIndicator />
          </Select.Item>
        </Select.Group>
        <Select.Separator />
      </Select.Viewport>
      <Select.ScrollDownButton />
    </Select.Content>
  </Select.Portal>
</Select.Root>
```

***

## Example

```tsx
import * as Select from '@gentleduck/primitives/select'

function ThemeSelect() {
  return (
    <Select.Root defaultValue="system">
      <Select.Trigger className="px-3 py-2 border rounded">
        <Select.Value placeholder="Pick a theme" />
        <Select.Icon />
      </Select.Trigger>

      <Select.Portal>
        <Select.Content position="popper" sideOffset={4} className="bg-white border rounded shadow-lg">
          <Select.Viewport className="p-1">
            <Select.Item value="light" className="px-3 py-1.5 rounded cursor-pointer hover:bg-gray-100">
              <Select.ItemText>Light</Select.ItemText>
              <Select.ItemIndicator>*</Select.ItemIndicator>
            </Select.Item>
            <Select.Item value="dark" className="px-3 py-1.5 rounded cursor-pointer hover:bg-gray-100">
              <Select.ItemText>Dark</Select.ItemText>
              <Select.ItemIndicator>*</Select.ItemIndicator>
            </Select.Item>
            <Select.Item value="system" className="px-3 py-1.5 rounded cursor-pointer hover:bg-gray-100">
              <Select.ItemText>System</Select.ItemText>
              <Select.ItemIndicator>*</Select.ItemIndicator>
            </Select.Item>
          </Select.Viewport>
        </Select.Content>
      </Select.Portal>
    </Select.Root>
  )
}
```

***

## API

### `Select.Root`

The root component that manages open/closed state, selected value, and provides context to all children. Also renders a hidden native `<select>` for form compatibility.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | - | Controlled selected value |
| `defaultValue` | `string` | - | Initial value (uncontrolled) |
| `onValueChange` | `(value: string) => void` | - | Called when the selected value changes |
| `open` | `boolean` | - | Controlled open state |
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
| `onOpenChange` | `(open: boolean) => void` | - | Called when the open state should change |
| `dir` | `'ltr' \| 'rtl'` | - | Text direction. Resolved with `useDirection` (`dir` prop -> `DirectionProvider` -> `'ltr'`). |
| `name` | `string` | - | Name for native form submission |
| `autoComplete` | `string` | - | Hint for browser autofill |
| `disabled` | `boolean` | - | Disables the select |
| `required` | `boolean` | - | Marks as required for form validation |
| `form` | `string` | - | Associates with a form element by ID |

### `Select.Trigger`

Button that toggles the dropdown. Renders a `<button>` with `role="combobox"`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | - | Render as the child element instead of a `<button>` |
| `disabled` | `boolean` | `false` | Disables the trigger |

Sets `aria-expanded`, `aria-controls`, `aria-autocomplete="none"`, `data-state`, and `data-placeholder` automatically.

### `Select.Value`

Displays the selected item text or a placeholder.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `placeholder` | `React.ReactNode` | `''` | Content shown when no value is selected |

### `Select.Icon`

Decorative icon next to the value. Renders a `<span>` with `aria-hidden`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | - | Render as child element |

### `Select.Portal`

Renders the dropdown content into a portal.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `container` | `Element \| null` | `document.body` | Portal target |

### `Select.Content`

The dropdown content. Handles focus trapping, dismiss-on-click-outside, escape-to-close, and keyboard navigation.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `position` | `'item-aligned' \| 'popper'` | `'item-aligned'` | Positioning strategy |
| `side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'bottom'` | Preferred side (popper only) |
| `sideOffset` | `number` | - | Main-axis offset (popper only) |
| `align` | `'start' \| 'center' \| 'end'` | `'start'` | Cross-axis alignment (popper only) |
| `alignOffset` | `number` | - | Cross-axis offset (popper only) |
| `avoidCollisions` | `boolean` | `true` | Flip to avoid viewport overflow (popper only) |
| `collisionBoundary` | `Element \| Element[]` | - | Custom collision boundary (popper only) |
| `collisionPadding` | `number` | `10` | Padding from viewport edges |
| `arrowPadding` | `number` | `0` | Minimum padding between arrow and content edges (popper only) |
| `sticky` | `'partial' \| 'always'` | `'partial'` | Keep content in view when trigger is partially hidden (popper only) |
| `hideWhenDetached` | `boolean` | `false` | Hide content when trigger is fully occluded (popper only) |
| `onCloseAutoFocus` | `(event: Event) => void` | - | Called when focus returns to trigger on close |
| `onEscapeKeyDown` | `(event: KeyboardEvent) => void` | - | Called when Escape is pressed |
| `onPointerDownOutside` | `(event) => void` | - | Called when clicking outside |
| `onFocusOutside` | `(event) => void` | - | Called when focus moves outside. Default prevents dismiss on focus out. |
| `trapFocus` | `boolean` | `context.open` | Override whether focus is trapped inside the content |
| `disableOutsidePointerEvents` | `boolean` | `context.open` | Override whether pointer events outside are blocked |
| `lockScroll` | `boolean` | `context.open` | Override whether body scroll is locked while open |

The `trapFocus`, `disableOutsidePointerEvents`, and `lockScroll` props default to the select's open state. Override them when integrating with animation libraries like [motion](https://motion.dev) that need custom lifecycle control during exit animations.

Exposes `data-state="open"` / `data-state="closed"` and `data-side` for CSS animation.

When using `position="popper"`, the following CSS custom properties are available:

* `--gentleduck-select-content-transform-origin`
* `--gentleduck-select-content-available-width`
* `--gentleduck-select-content-available-height`
* `--gentleduck-select-trigger-width`
* `--gentleduck-select-trigger-height`

### `Select.Viewport`

Scrollable container for items inside the content.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `nonce` | `string` | - | Nonce for the injected scrollbar-hiding style element |

### `Select.Group`

Groups related items. Renders a `<div>` with `role="group"`.

### `Select.Label`

Label for a group. Linked via `aria-labelledby`.

### `Select.Item`

A selectable item. Renders a `<div>` with `role="option"`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | (required) | The value for this item. Must not be empty string. |
| `disabled` | `boolean` | `false` | Disables the item |
| `textValue` | `string` | - | Text override for typeahead search (defaults to text content) |

Exposes `data-state="checked"` / `data-state="unchecked"`, `data-highlighted`, and `data-disabled`.

### `Select.ItemText`

The text portion of an item. When selected, the text content is portalled into the `Value` component.

### `Select.ItemIndicator`

Renders only when the parent item is selected. Use for check marks or other visual indicators.

### `Select.ScrollUpButton` / `Select.ScrollDownButton`

Scroll buttons that appear when content overflows. Auto-scroll on hover.

### `Select.Separator`

Visual separator between items. Renders a `<div>` with `aria-hidden`.

### `Select.Arrow`

Arrow pointing to the trigger. Only renders when `position="popper"`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `width` | `number` | `10` | Arrow width in pixels |
| `height` | `number` | `5` | Arrow height in pixels |

***

## Keyboard interactions

| Key | Action |
| --- | --- |
| <Kbd>Space</Kbd> / <Kbd>Enter</Kbd> | Opens the select (on trigger) or selects focused item |
| <Kbd>ArrowDown</Kbd> | Opens the select or moves focus to next item |
| <Kbd>ArrowUp</Kbd> | Opens the select or moves focus to previous item |
| <Kbd>Home</Kbd> | Moves focus to first item |
| <Kbd>End</Kbd> | Moves focus to last item |
| <Kbd>Escape</Kbd> | Closes the select |
| <Kbd>Tab</Kbd> | Prevented while open (standard select behavior) |
| Type characters | Typeahead search - focuses matching items |