<!-- Protocore Design System — Select -->
# Select

- **Category:** Inputs (`inputs`)
- **Slug:** `inputs/select`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { Select } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/inputs/select

> Input-styled trigger plus an overlay panel for choosing one value from a list; flat or compound API.

## When to use it

Use **Select** for choosing one value from a **known, moderate** list (roughly 5–30 options) that doesn't need to stay on screen. When the list is short and worth showing in full, a **RadioGroup** saves a click; when it's just 2–4 modes, use **SegmentedControl**. When the list is long or benefits from type-ahead filtering, reach for **Combobox** instead. For selecting *many* values, use a multi-select **FilterBar** or a set of **Checkbox**es.

## Props

### Select

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `aria-describedby` | `string` | no | — | Ids of elements describing the trigger (e.g. a `<Field>` hint/error). |
| `aria-label` | `string` | no | — | Accessible label for the trigger when no visible `<Field>` label wraps it. |
| `aria-labelledby` | `string` | no | — | Ids of elements labelling the trigger (e.g. a `<Field>` label). |
| `className` | `string` | no | — | Class applied to the trigger. |
| `defaultValue` | `string` | no | — | — |
| `id` | `string` | no | — | Id applied to the trigger button — wired automatically when wrapped in `<Field>`. |
| `invalid` | `boolean` | no | — | Mark the trigger invalid. |
| `onValueChange` | `((value: string) => void)` | no | — | — |
| `options` | `SelectOption[]` | yes | — | Options to render; use the compound API instead for groups or custom item content. |
| `placeholder` | `ReactNode` | no | — | Placeholder shown when no value is selected. |
| `size` | `enum` | no | `md` | Trigger height. |
| `value` | `string` | no | — | — |

### SelectTrigger

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asChild` | `boolean` | no | — | — |
| `className` | `string` | no | — | — |
| `invalid` | `boolean` | no | — | Mark the trigger invalid — danger border plus `aria-invalid`. |
| `size` | `enum` | no | `md` | Trigger height: `sm` (32) · `md` (36, default) · `lg` (40). |
| `style` | `CSSProperties` | no | — | — |

### SelectItem

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asChild` | `boolean` | no | — | — |
| `className` | `string` | no | — | — |
| `style` | `CSSProperties` | no | — | — |

## Examples

### Basics

The one-prop form: pass `options` and `Select` renders the trigger and panel for you. Controllable via `value` / `defaultValue` / `onValueChange`.

```tsx
import { Select } from "@protocore/pds";

const REGIONS = [
  { value: "eu-central-1", label: "eu-central-1 · Frankfurt" },
  { value: "us-east-1", label: "us-east-1 · N. Virginia" },
  { value: "ap-southeast-2", label: "ap-southeast-2 · Sydney" },
];

export default function Demo() {
  return (
    <div style={{ width: 260 }}>
      <Select.Root defaultValue="eu-central-1">
        <Select.Trigger aria-label="Deploy region">
          <Select.Value />
        </Select.Trigger>
        <Select.Content>
          {REGIONS.map((o) => (
            <Select.Item key={o.value} value={o.value}>
              {o.label}
            </Select.Item>
          ))}
        </Select.Content>
      </Select.Root>
    </div>
  );
}
```

### Groups & separators

Drop to the compound API — `Select.Root`, `Trigger`, `Content`, `Group`, `Label`, `Separator`, `Item` — when you need captioned groups or custom item content.

```tsx
import { Select } from "@protocore/pds";

export default function Demo() {
  return (
    <div style={{ width: 260 }}>
      <Select.Root defaultValue="t3.small">
        <Select.Trigger aria-label="Instance type">
          <Select.Value placeholder="Pick an instance…" />
        </Select.Trigger>
        <Select.Content>
          <Select.Group>
            <Select.Label>Burstable</Select.Label>
            <Select.Item value="t3.small">t3.small · 2 vCPU · 2 GiB</Select.Item>
            <Select.Item value="t3.medium">t3.medium · 2 vCPU · 4 GiB</Select.Item>
          </Select.Group>
          <Select.Separator />
          <Select.Group>
            <Select.Label>Compute optimized</Select.Label>
            <Select.Item value="c7g.large">c7g.large · 2 vCPU · 4 GiB</Select.Item>
            <Select.Item value="c7g.xlarge">c7g.xlarge · 4 vCPU · 8 GiB</Select.Item>
          </Select.Group>
        </Select.Content>
      </Select.Root>
    </div>
  );
}
```

## Do & don't

**Do**

- Give the trigger an accessible name via a `Field` label or `aria-label`.
- Provide a `placeholder` when there's no sensible default selection.
- Use `Select.Group` + `Select.Label` to structure long option lists.
- Keep option `value`s stable and non-empty — Radix disallows an empty string value.

**Don't**

- Don't use Select when users need to filter by typing — use Combobox.
- Don't cram 2–3 options into a Select — a SegmentedControl or RadioGroup reads faster.
- Don't put interactive controls (buttons, links) inside a `Select.Item`.
- Don't leave the trigger unlabelled when no visible field label wraps it.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Space / Enter / Arrow Down` | Open the panel from the focused trigger. |
| `Arrow Up / Down` | Move between options while open. |
| `Enter` | Select the highlighted option and close. |
| `Type a letter` | Jump to the next option starting with that character. |
| `Esc` | Close the panel without changing the value. |

**Notes**

- Built on Radix Select — the panel is portalled, focus is trapped while open, and focus returns to the trigger on close.
- The trigger exposes `aria-expanded`; the active option carries `aria-selected` with a check indicator.
- `invalid` sets `aria-invalid` on the trigger for assistive tech.

## Related

`combobox`, `radio-group`, `segmented-control`, `field`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
