# Select

Picks one value from a list. The trigger looks like Input, in the same four sizes; the list opens over it on the raised surface, with the current value aligned to the trigger and marked with a check.

## Usage

### When to use

- Pick one value from a known list of options, such as the role of a team member.
- Fit a choice into the space of one field, next to an Input or Button of the same size.
- A longer list split into labelled groups, with a line between them.

### When not to use

- A yes or no choice. Use a native checkbox.
- Going to another page or running an action. Use a link or Button.
- Text the user types freely. Use Input.

## Variants

### size

Trigger height from size-element-*, set on SelectTrigger: sm 28px, md 32px (the default), lg 36px, xl 44px with a mouse; on touch md is 36px, lg 44px and xl 48px. Match the Input or Button next to it: md in forms, sm only in dense mouse-first UI, lg or xl for the main field of a screen.

Values: `sm`, `md`, `lg`, `xl`. Default: `md`.

### dir

Set on Select. Reading direction of the list and its keyboard. Leave it out to follow the page.

Values: `ltr`, `rtl`. Default: `undefined`.

## Examples

### Default

```tsx
<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="member-role" className="text-sm text-text-primary">Role</label>
  <Select>
    <SelectTrigger id="member-role">
      <SelectValue placeholder="Choose a role" />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="viewer">Viewer</SelectItem>
      <SelectItem value="editor">Editor</SelectItem>
      <SelectItem value="admin">Admin</SelectItem>
    </SelectContent>
  </Select>
</div>
```

### Grouped options

Each group is labelled by its heading; the line between groups is decorative.

```tsx
<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="member-access" className="text-sm text-text-primary">Access</label>
  <Select>
    <SelectTrigger id="member-access">
      <SelectValue placeholder="Choose access" />
    </SelectTrigger>
    <SelectContent>
      <SelectGroup>
        <SelectLabel>Workspace</SelectLabel>
        <SelectItem value="viewer">Viewer</SelectItem>
        <SelectItem value="editor">Editor</SelectItem>
      </SelectGroup>
      <SelectSeparator />
      <SelectGroup>
        <SelectLabel>Billing</SelectLabel>
        <SelectItem value="billing-viewer">Billing viewer</SelectItem>
        <SelectItem value="billing-admin">Billing admin</SelectItem>
      </SelectGroup>
    </SelectContent>
  </Select>
</div>
```

### Disabled

Keeps its value visible but can't be opened.

```tsx
<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="locked-role" className="text-sm text-text-primary">Role</label>
  <Select defaultValue="owner" disabled>
    <SelectTrigger id="locked-role">
      <SelectValue />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="owner">Owner</SelectItem>
    </SelectContent>
  </Select>
</div>
```

### Invalid

The danger border plus error text linked with aria-describedby.

```tsx
<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="invite-role" className="text-sm text-text-primary">Role</label>
  <Select>
    <SelectTrigger id="invite-role" aria-invalid aria-describedby="invite-role-error">
      <SelectValue placeholder="Choose a role" />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="viewer">Viewer</SelectItem>
      <SelectItem value="editor">Editor</SelectItem>
    </SelectContent>
  </Select>
  <p id="invite-role-error" className="text-sm text-text-danger">Choose a role for the new member.</p>
</div>
```

## Do and don't

### Do

- Use Select to pick one value from a known list of options.
- Always pair the trigger with a visible label (htmlFor / id).
- Keep option labels short and in sentence case, and sort them in a way the user expects (by order of use, or alphabetically).
- Use a placeholder only to hint at the action (Choose a role), never instead of the label.
- Match the trigger size to the Input or Button next to it.
- Split a long list into groups with a SelectLabel heading each (Workspace, Billing), and a SelectSeparator between them.

### Don't

- Don't use Select for a yes or no choice. Use a checkbox.
- Don't use Select to navigate or to trigger actions. Use links or Button.
- Don't put icons, descriptions or other controls inside options; an option is one line of text.
- Don't use size sm on touch-first screens.
- Don't restyle colors through className. If a state looks wrong, open an issue instead of overriding it.

## Props

| Name | Type | Values | Default | Description |
| --- | --- | --- | --- | --- |
| `size` | `enum` | `sm`, `md`, `lg`, `xl` | `md` | Trigger height from size-element-*, set on SelectTrigger: sm 28px, md 32px (the default), lg 36px, xl 44px with a mouse; on touch md is 36px, lg 44px and xl 48px. Match the Input or Button next to it: md in forms, sm only in dense mouse-first UI, lg or xl for the main field of a screen. |
| `disabled` | `boolean` |  | `false` | Set on Select. The trigger can't be focused or opened and uses the neutral disabled look. |
| `placeholder` | `string` |  |  | Set on SelectValue. Shown in the trigger until a value is picked. Not a label. |
| `defaultValue` | `string` |  |  | Set on Select. The value picked on first render, when you don't control it. |
| `value` | `string` |  |  | Set on Select. The picked value, when you control it. Pair it with onValueChange. |
| `onValueChange` | `function` |  |  | Set on Select. Called with the new value when the user picks an option. |
| `name` | `string` |  |  | Set on Select. Submits the value with a form under this name. |
| `required` | `boolean` |  | `false` | Set on Select. A form can't be submitted until a value is picked. Say it in the label too, not only with this prop. |
| `open` | `boolean` |  |  | Set on Select. Whether the list is open, when you control it. Pair it with onOpenChange. |
| `defaultOpen` | `boolean` |  | `false` | Set on Select. Opens the list on first render, when you don't control it. |
| `onOpenChange` | `function` |  |  | Set on Select. Called with true or false when the list opens or closes. |
| `dir` | `enum` | `ltr`, `rtl` |  | Set on Select. Reading direction of the list and its keyboard. Leave it out to follow the page. |

### Slots

| Name | Required | Description |
| --- | --- | --- |
| `trigger` | yes | SelectTrigger with a SelectValue inside. |
| `content` | yes | SelectContent with one SelectItem per option. Item labels are short nouns in sentence case. Long lists: SelectGroup with a SelectLabel per group, SelectSeparator between groups. |

## Anatomy

| Part | Description |
| --- | --- |
| `root` | Select. Holds the value and the open state; renders nothing itself. |
| `trigger` | SelectTrigger: the button that shows the value and opens the list. Same fill, border and sizes as Input. |
| `value` | SelectValue: the current option's text, or the placeholder when nothing is picked. |
| `icon` | Tabler ChevronDownIcon at the end of the trigger. 16px in sm, md and lg, 20px in xl (DESIGN.md 9.1). |
| `content` | SelectContent: the floating list on the raised surface, at least as wide as the trigger. Long lists scroll inside it. |
| `item` | SelectItem: one option. Highlighted on hover and with the arrow keys. |
| `indicator` | Tabler CheckIcon (16px) at the end of the selected option. |
| `optionGroup` | SelectGroup: options under one heading. Renders no box of its own. |
| `groupLabel` | SelectLabel: the heading of a group, smaller than an option, in secondary text. Not selectable. |
| `separator` | SelectSeparator: a line across the full list between two groups. |
| `scrollButton` | SelectContent shows a caret at the top or bottom while more options are out of view; pointing at it scrolls the list. Tabler ChevronUpIcon or CaretDownIcon, 16px. |
| `hitArea` | Transparent ::before centered on the trigger, never smaller than size-target-min (24px on mouse, 44px on touch), so sm stays a 44px touch target. |

## Accessibility

Role: `combobox`

| Keys | Action |
| --- | --- |
| `Tab` | Moves focus to the trigger and shows the focus ring. |
| `Enter, Space, ArrowDown, ArrowUp` | Opens the list with the current value highlighted. |
| `ArrowDown, ArrowUp` | Moves the highlight while the list is open. |
| `Home, End` | Highlights the first or last option. |
| `Enter, Space` | Picks the highlighted option, closes the list and returns focus to the trigger. |
| `Escape` | Closes the list without changing the value and returns focus to the trigger. |
| `A to Z` | Highlights the next option that starts with the typed letters. |

- Give the trigger a visible label: an id on SelectTrigger and a label with htmlFor. aria-label only where the label is shown another way, such as a table column header.
- Invalid: aria-invalid on SelectTrigger plus error text linked with aria-describedby. Never color alone.
- Disabled uses the native disabled attribute on the trigger, so it leaves the tab order.
- The trigger is a button with role combobox and the list a listbox; options are announced with their selected state.
- Single value only. For several values use a group of checkboxes.
- SelectGroup has role group and is labelled by its SelectLabel, so a screen reader announces the heading when the highlight enters the group. SelectLabel is not focusable and the arrow keys skip it.
- SelectSeparator is decorative (aria-hidden).

## Install

```bash
npx shadcn add https://werewolf-ui.vercel.app/r/select.json
```

This also adds `styles/werewolf.css`, the tokens every component uses, and any other component it needs. Import that file once in your root CSS, after `@import "tailwindcss"`, with a path relative to that CSS file (from `app/globals.css` it is `@import "../styles/werewolf.css"`).
