# Select

> Choose one value from a list, with typeahead, groups, disabled options and form submission.

Category: Forms · Status: stable · Package: @bindshq/stance-react@0.1.1 · Docs: https://stanceui.com/components/select

Use Select when the user picks exactly one value from a known list of roughly 5 to 30 options. For long or searchable lists use Combobox; for several values use MultiSelect; for 2 to 4 visible choices consider RadioGroup. Select is a select-only combobox (WAI-ARIA APG): focus stays on the trigger and the active option is announced through aria-activedescendant. It integrates with FormField and submits through a hidden input.

## Import

```tsx
import { Select, type SelectOptions } from '@bindshq/stance-react';
```

Styles are imported once per app: `@bindshq/stance-tokens/css` and `@bindshq/stance-react/styles.css`.

## Examples

### In a form field

```tsx
import { FormField, Select } from '@bindshq/stance-react';
import type { SelectOptions } from '@bindshq/stance-react';

const roles: SelectOptions = [
  { value: 'owner', label: 'Owner', description: 'Full access, including billing' },
  { value: 'admin', label: 'Admin', description: 'Manage users, templates and integrations' },
  { value: 'editor', label: 'Editor', description: 'Create and edit templates' },
  { value: 'viewer', label: 'Viewer', description: 'Read-only access' },
];

export function Basic() {
  return (
    <div style={{ width: 320 }}>
      <FormField label="Role" description="Controls what this member can change.">
        <Select options={roles} placeholder="Choose a role" name="role" />
      </FormField>
    </div>
  );
}
```

### Grouped options

```tsx
import { FormField, Select } from '@bindshq/stance-react';

export function Groups() {
  return (
    <div style={{ width: 320 }}>
      <FormField label="Default timezone">
        <Select
          defaultValue="europe-london"
          options={[
            {
              label: 'Europe',
              options: [
                { value: 'europe-london', label: 'London (GMT)' },
                { value: 'europe-paris', label: 'Paris (CET)' },
                { value: 'europe-berlin', label: 'Berlin (CET)' },
              ],
            },
            {
              label: 'Asia',
              options: [
                { value: 'asia-kolkata', label: 'Kolkata (IST)' },
                { value: 'asia-singapore', label: 'Singapore (SGT)' },
                { value: 'asia-dubai', label: 'Dubai (GST)' },
              ],
            },
          ]}
        />
      </FormField>
    </div>
  );
}
```

### Icons and disabled options

```tsx
import { FormField, Icon, Select } from '@bindshq/stance-react';

export function WithIcons() {
  return (
    <div style={{ width: 320 }}>
      <FormField label="Deploy through">
        <Select
          defaultValue="exchange"
          options={[
            { value: 'exchange', label: 'Exchange Online', icon: <Icon name="mail" /> },
            { value: 'relay', label: 'Outlook relay', icon: <Icon name="send" /> },
            {
              value: 'gmail',
              label: 'Google Workspace (coming soon)',
              icon: <Icon name="globe" />,
              disabled: true,
            },
          ]}
        />
      </FormField>
    </div>
  );
}
```

### Sizes

```tsx
import { Select } from '@bindshq/stance-react';
import type { SelectOptions } from '@bindshq/stance-react';

const roles: SelectOptions = [
  { value: 'owner', label: 'Owner', description: 'Full access, including billing' },
  { value: 'admin', label: 'Admin', description: 'Manage users, templates and integrations' },
  { value: 'editor', label: 'Editor', description: 'Create and edit templates' },
  { value: 'viewer', label: 'Viewer', description: 'Read-only access' },
];

export function Sizes() {
  return (
    <div style={{ display: 'grid', gap: 12, width: 280 }}>
      <Select aria-label="Small" size="sm" options={roles} defaultValue="editor" />
      <Select aria-label="Medium" size="md" options={roles} defaultValue="editor" />
      <Select aria-label="Large" size="lg" options={roles} defaultValue="editor" />
    </div>
  );
}
```

### Controlled

```tsx
import * as React from 'react';
import { FormField, Select } from '@bindshq/stance-react';
import type { SelectOptions } from '@bindshq/stance-react';

const roles: SelectOptions = [
  { value: 'owner', label: 'Owner', description: 'Full access, including billing' },
  { value: 'admin', label: 'Admin', description: 'Manage users, templates and integrations' },
  { value: 'editor', label: 'Editor', description: 'Create and edit templates' },
  { value: 'viewer', label: 'Viewer', description: 'Read-only access' },
];

export function Controlled() {
  const [role, setRole] = React.useState<string | null>('viewer');
  return (
    <div style={{ display: 'grid', gap: 8, width: 320 }}>
      <FormField label="Role">
        <Select options={roles} value={role} onValueChange={setRole} />
      </FormField>
      <span style={{ color: 'var(--st-color-fg-muted)', fontSize: 'var(--st-font-size-sm)' }}>
        Selected: {role ?? 'none'}
      </span>
    </div>
  );
}
```

### Error and disabled

```tsx
import { FormField, Select } from '@bindshq/stance-react';

export function States() {
  return (
    <div style={{ display: 'grid', gap: 16, width: 320 }}>
      <FormField label="Department" error="Choose a department to continue" required>
        <Select
          options={[
            { value: 'legal', label: 'Legal' },
            { value: 'sales', label: 'Sales' },
          ]}
          placeholder="Choose a department"
        />
      </FormField>
      <FormField label="Plan" disabled>
        <Select options={[{ value: 'business', label: 'Business' }]} defaultValue="business" />
      </FormField>
    </div>
  );
}
```

## API reference

| Component | Prop | Type | Default | Description |
| --- | --- | --- | --- | --- |
| Select | `options` (required) | `SelectOptions` | None | Options `{ value, label, description?, icon?, disabled? }`, or groups `{ label, options }`. |
| Select | `value` | `string \| null` | None | Selected value (controlled). |
| Select | `defaultValue` | `string \| null` | `null` | Initial value when uncontrolled. |
| Select | `onValueChange` | `(value: string) => void` | None | Called when the user picks an option. |
| Select | `placeholder` | `string` | `'Select…'` | Shown when nothing is selected. |
| Select | `name` | `string` | None | Form field name; a hidden input carries the value. |
| Select | `form` | `string` | None | Associate with a form by id. |
| Select | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Control height: 32, 40 or 48px. |
| Select | `required / disabled / invalid` | `boolean` | None | Also inherited from FormField. |
| Select | `open / defaultOpen / onOpenChange` | `boolean / function` | None | Popup open state. |
| Select | `renderOption` | `(option, { selected, active }) => ReactNode` | None | Custom option content. |
| Select | `emptyText` | `ReactNode` | `'No options'` | Shown when options is empty. |
| Select | `contentClassName` | `string` | None | Class for the popup. |
| Select | `...props` | `ButtonHTMLAttributes` | None | Native attributes for the trigger, e.g. aria-label when used without FormField. |

## Accessibility

- The trigger is a button with role="combobox", aria-haspopup="listbox", aria-expanded and aria-controls.
- Inside FormField it is labelled by the visible label and described by the description and error.
- Options use role="option" with aria-selected; groups use role="group" labelled by their heading.
- Focus never leaves the trigger; the active option is conveyed through aria-activedescendant.
- Disabled options expose aria-disabled and are skipped by arrow keys and typeahead.

## Keyboard

| Keys | Action |
| --- | --- |
| `Enter` `Space` `ArrowDown` `ArrowUp` | Open the list at the selected option. |
| `Home` `End` | Open at, or move to, the first or last option. |
| `ArrowDown` `ArrowUp` | Move the active option. |
| `PageDown` `PageUp` | Move by ten options. |
| `A to Z` | Typeahead by label, whether open or closed. |
| `Enter` `Space` | Select the active option and close. |
| `Tab` | Select the active option and move on. |
| `Escape` | Close without changing the value. |

## Guidelines

**Do**

- Order options logically: alphabetically, by frequency, or by magnitude.
- Use a placeholder that tells people what to choose.

**Don’t**

- Use Select for fewer than three options; use RadioGroup or a Switch.
- Use Select for long lists people need to search; use Combobox.

## Related

- https://stanceui.com/components/combobox
- https://stanceui.com/components/multi-select
- https://stanceui.com/components/native-select
- https://stanceui.com/components/radio-group
- https://stanceui.com/components/form-field
