# Native select

A styled browser select, for short lists where the platform's own picker is best.

- **Import:** `import { NativeSelect, NativeSelectOptGroup, NativeSelectOption } from "@booleanpress/ui/native-select"`
- **APG Combobox, select-only:** <https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/>
- **Page:** <https://ui.booleanpress.com/components/native-select> · @booleanpress/ui 0.1.0

## Usage

`NativeSelect` wraps a native `select` and adds a chevron. Fill it with `NativeSelectOption` and, to group, `NativeSelectOptGroup`. Name it with a `Label` whose `htmlFor` is its `id`.

```tsx
import { Label } from "@booleanpress/ui/label"
import { NativeSelect, NativeSelectOption } from "@booleanpress/ui/native-select"

export function Encryption() {
  return (
    <div className="flex flex-col gap-2">
      <Label htmlFor="encryption">Encryption</Label>
      <NativeSelect id="encryption" defaultValue="tls">
        <NativeSelectOption value="none">None</NativeSelectOption>
        <NativeSelectOption value="tls">TLS</NativeSelectOption>
      </NativeSelect>
    </div>
  )
}
```

It takes every attribute of a native `select`, and `size` is `default` (36 px) or `sm` (32 px) rather than the native row count. Use `defaultValue` or `value` with `onChange` (the value is `event.target.value`). The wrapper is `w-fit`; widen it with a class on the `select` and a wrapper width. Use `Select` instead when options need icons, rich content or search.

## Examples

### Basic

A labelled select with three options.

```tsx
import { Label } from "@booleanpress/ui/label"
import { NativeSelect, NativeSelectOption } from "@booleanpress/ui/native-select"

export default function NativeSelectBasic() {
  return (
    <div className="flex flex-col gap-2">
      <Label htmlFor="encryption">Encryption</Label>
      <NativeSelect id="encryption" defaultValue="tls">
        <NativeSelectOption value="none">None</NativeSelectOption>
        <NativeSelectOption value="tls">TLS</NativeSelectOption>
        <NativeSelectOption value="ssl">SSL</NativeSelectOption>
      </NativeSelect>
    </div>
  )
}
```

### Sizes

`default` is 36 px high, `sm` is 32 px.

```tsx
import { NativeSelect, NativeSelectOption } from "@booleanpress/ui/native-select"

export default function NativeSelectSizes() {
  return (
    <div className="flex flex-col items-start gap-4">
      <NativeSelect aria-label="Status, default size" defaultValue="all">
        <NativeSelectOption value="all">All statuses</NativeSelectOption>
        <NativeSelectOption value="delivered">Delivered</NativeSelectOption>
      </NativeSelect>
      <NativeSelect size="sm" aria-label="Status, small size" defaultValue="all">
        <NativeSelectOption value="all">All statuses</NativeSelectOption>
        <NativeSelectOption value="delivered">Delivered</NativeSelectOption>
      </NativeSelect>
    </div>
  )
}
```

### Groups

`NativeSelectOptGroup` labels sets of options; a disabled first option is the prompt.

```tsx
import { Label } from "@booleanpress/ui/label"
import { NativeSelect, NativeSelectOptGroup, NativeSelectOption } from "@booleanpress/ui/native-select"

export default function NativeSelectGroups() {
  return (
    <div className="flex flex-col gap-2">
      <Label htmlFor="mailer">Mailer</Label>
      <NativeSelect id="mailer" defaultValue="">
        <NativeSelectOption value="" disabled>
          Choose a mailer
        </NativeSelectOption>
        <NativeSelectOptGroup label="API">
          <NativeSelectOption value="ses">Amazon SES</NativeSelectOption>
          <NativeSelectOption value="sendgrid">SendGrid</NativeSelectOption>
        </NativeSelectOptGroup>
        <NativeSelectOptGroup label="SMTP">
          <NativeSelectOption value="smtp">Custom SMTP</NativeSelectOption>
        </NativeSelectOptGroup>
      </NativeSelect>
    </div>
  )
}
```

### Disabled

The select and its chevron dim; it leaves the tab order.

```tsx
import { Label } from "@booleanpress/ui/label"
import { NativeSelect, NativeSelectOption } from "@booleanpress/ui/native-select"

export default function NativeSelectDisabled() {
  return (
    <div className="flex flex-col gap-2">
      <Label htmlFor="locked-region">Region</Label>
      <NativeSelect id="locked-region" defaultValue="eu" disabled>
        <NativeSelectOption value="eu">Europe</NativeSelectOption>
        <NativeSelectOption value="us">United States</NativeSelectOption>
      </NativeSelect>
    </div>
  )
}
```

### Invalid

`aria-invalid` draws the error border; `aria-describedby` reads the message with the name.

```tsx
import { Label } from "@booleanpress/ui/label"
import { NativeSelect, NativeSelectOption } from "@booleanpress/ui/native-select"

export default function NativeSelectInvalid() {
  return (
    <div className="flex flex-col gap-2">
      <Label htmlFor="priority">Priority</Label>
      <NativeSelect id="priority" defaultValue="" aria-invalid aria-describedby="priority-error">
        <NativeSelectOption value="">Choose one</NativeSelectOption>
        <NativeSelectOption value="normal">Normal</NativeSelectOption>
        <NativeSelectOption value="urgent">Urgent</NativeSelectOption>
      </NativeSelect>
      <p id="priority-error" className="text-sm text-destructive">
        Choose a priority.
      </p>
    </div>
  )
}
```

## Accessibility

**Semantics.** A native `select` (role `combobox`, or `listbox` with a row count) with native `option` and `optgroup`. The chevron is `aria-hidden`.

**Labels.** Name every select: a `Label` with `htmlFor`, or `aria-label`. Put errors in `aria-describedby`.

**Focus.** It is in the tab order unless disabled. The focus ring shows on keyboard focus.

**Known limits.**

- The open list is drawn by the browser and the operating system, so its look cannot be themed beyond `Canvas` colours.

### Keyboard

| Key | Behaviour |
| --- | --- |
| Arrow Down + Arrow Up | Changes the chosen option, or moves through the list when it is open. The browser decides which. |
| Alt + Arrow Down | Opens the list, where the browser supports it. |
| Letters | Jump to the next option that starts with the typed text. |

## API

### NativeSelect

Renders a `select` and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `"default" \| "sm"` | `default` | `default` is 36 px high; `sm` is 32 px. |

### NativeSelectOptGroup

Renders a `optgroup` and passes it every other prop.

### NativeSelectOption

Renders a `option` and passes it every other prop.

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="native-select-wrapper"` (NativeSelect), `data-slot="native-select-optgroup"` (NativeSelectOptGroup), `data-slot="native-select-option"` (NativeSelectOption), and `data-size`.

## Theming

| Token | Used for |
| --- | --- |
| `--control` | border |
| `--destructive` | border, focus ring |
| `--input` | border, background |
| `--muted-foreground` | text |
| `--primary` | background |
| `--primary-foreground` | text |
| `--ring` | border, focus ring |
