# Dropdown

_Box Kite 2.2.5 · a markdown copy of https://www.box-kite.dev/dropdown/_

Use Dropdown component to choose option(s) from a list.

**Import**

```jsx
import Dropdown from '@box-kite/react/components/dropdown';
```

**Basic Dropdown**

```jsx
<Dropdown label="Choice" defaultValue={1}>
  <Dropdown.Item value={1}>Option 1</Dropdown.Item>
  <Dropdown.Item value={2}>Option 2</Dropdown.Item>
</Dropdown>
```

## Keyboard and roles

This is the APG combobox, whole — in both of its shapes. The trigger is a `role="combobox"` that keeps DOM focus the entire time; the popup is a `role="listbox"` of `role="option"` rows, and the option the arrows are on is named by `aria-activedescendant` rather than focused. Add `isSearchable` and the combobox becomes the text field instead of a button. You write none of that — and if you were writing it by hand before, delete it.

Give it a label

A combobox is not named by what it contains: the text in the trigger is its _value_. Pass `label` and the component renders it and wires `aria-labelledby` — or pass your own `aria-label` in `props`. Without either, the control has no accessible name, exactly like an input with only a placeholder.

Clear and Select all are options too

`Dropdown.Unselect` and `Dropdown.SelectAll` sit inside the listbox, so they carry `role="option"` and the arrow keys reach them like any other row.

Searchable is the editable combobox

With `isSearchable` the text field _is_ the combobox — the role, the ARIA and anything you pass in `props` live on the input, so nothing focusable sits inside anything else focusable. It is a different keyboard map — the one under `With isSearchable` in the keyboard reference below: the printable keys type instead of navigating, and only Down and Up reach the listbox.

**Controlled Dropdown**

```jsx
const [selectedValue, setSelectedValue] = useState<number>(2);

<Dropdown value={selectedValue} onValueChange={(value) => setSelectedValue(value as number)}>
  <Dropdown.Item value={1}>Option 1</Dropdown.Item>
  <Dropdown.Item value={2}>Option 2</Dropdown.Item>
  <Dropdown.Item value={3}>Option 3</Dropdown.Item>
</Dropdown>
```

**Unselect Item**

```jsx
<Dropdown>
  <Dropdown.Unselect>Select</Dropdown.Unselect>
  <Dropdown.Item value={1}>Option 1</Dropdown.Item>
  <Dropdown.Item value={2}>Option 2</Dropdown.Item>
</Dropdown>
```

**Disabled**

```jsx
<Dropdown disabled width={50}>
  <DropdownUnselect>Select</DropdownUnselect>
  <DropdownItem value={1}>Option 1</DropdownItem>
  <DropdownItem value={2}>Option 2</DropdownItem>
</Dropdown>
```

**Compact**

```jsx
<Dropdown variant="compact" width={40}>
  <DropdownUnselect>Select</DropdownUnselect>
  <DropdownItem value={1}>Option 1</DropdownItem>
  <DropdownItem value={2}>Option 2</DropdownItem>
</Dropdown>
```

**Custom Variant (outlined)**

```jsx
// boxExtends.ts — register the variant, and export what you registered.
export const components = Box.components({
  dropdown: {
    variants: {
      outlined: {
        bgColor: 'transparent', b: 2, borderColor: 'indigo-500', color: 'indigo-600',
      },
    },
    children: {
      item: { variants: { outlined: { hover: { bgColor: 'indigo-50' }, selected: { bgColor: 'indigo-100' } } } },
      items: { variants: { outlined: { b: 2, borderColor: 'indigo-500' } } },
    },
  },
});

// box.d.ts — teach TypeScript the name, or variant="outlined" stays a type error.
import { ExtractComponentsAndVariants } from '@box-kite/core/types';
import { components } from './boxExtends';

declare module '@box-kite/core/types' {
  namespace Augmented {
    interface ComponentsTypes extends ExtractComponentsAndVariants<typeof components> {}
  }
}

// Use — the variant propagates to all children automatically.
<Dropdown variant="outlined">
  <Dropdown.Item value={1}>Option 1</Dropdown.Item>
  <Dropdown.Item value={2}>Option 2</Dropdown.Item>
</Dropdown>
```

**Searchable**

```jsx
<Dropdown
  label="User"
  isSearchable
  searchPlaceholder="Search users..."
  width={50}
>
  <DropdownUnselect>Select</DropdownUnselect>
  <DropdownItem value={1}>John Doe</DropdownItem>
  <DropdownItem value={2}>Joe Smith</DropdownItem>
  <DropdownItem value={3}>Alice</DropdownItem>
  <DropdownItem value={4}>Bob</DropdownItem>
</Dropdown>
```

**Searchable with Empty Item**

```jsx
<Dropdown
  label="User"
  isSearchable
  searchPlaceholder="Search users..."
  width={50}
>
  <DropdownEmptyItem>No options</DropdownEmptyItem>
  <DropdownUnselect>Select</DropdownUnselect>
  <DropdownItem value={1}>John Doe</DropdownItem>
  <DropdownItem value={2}>Joe Smith</DropdownItem>
  <DropdownItem value={3}>Alice</DropdownItem>
  <DropdownItem value={4}>Bob</DropdownItem>
</Dropdown>
```

**Multiple Selection**

```jsx
<Dropdown multiple width={50}>
  <DropdownEmptyItem>No options</DropdownEmptyItem>
  <DropdownUnselect>Unselect All</DropdownUnselect>
  <DropdownSelectAll>Select All</DropdownSelectAll>
  <DropdownItem value={1}>John Doe</DropdownItem>
  <DropdownItem value={2}>Joe Smith</DropdownItem>
  <DropdownItem value={3}>Alice</DropdownItem>
  <DropdownItem value={4}>Bob</DropdownItem>
</Dropdown>
```

**Multiple Selection with Checkboxes**

```jsx
<Dropdown multiple showCheckbox width={50}>
  <DropdownEmptyItem>No options</DropdownEmptyItem>
  <DropdownUnselect>Unselect All</DropdownUnselect>
  <DropdownSelectAll>Select All</DropdownSelectAll>
  <DropdownItem value={1}>John Doe</DropdownItem>
  <DropdownItem value={2}>Joe Smith</DropdownItem>
  <DropdownItem value={3}>Alice</DropdownItem>
  <DropdownItem value={4}>Bob</DropdownItem>
</Dropdown>
```

**Multiple with Custom Display**

```jsx
<Dropdown multiple showCheckbox width={50}>
  <DropdownDisplay />
  <DropdownEmptyItem>No options</DropdownEmptyItem>
  <DropdownUnselect>Unselect All</DropdownUnselect>
  <DropdownSelectAll>Select All</DropdownSelectAll>
  <DropdownItem value={1}>John Doe</DropdownItem>
  <DropdownItem value={2}>Joe Smith</DropdownItem>
  <DropdownItem value={3}>Alice</DropdownItem>
  <DropdownItem value={4}>Bob</DropdownItem>
</Dropdown>
```

### Select

Data-driven dropdown — pass data + def instead of composing children. Wraps Dropdown internally.

**Import**

```jsx
import Select from '@box-kite/react/components/select';
```

**Basic Select**

```jsx
const users = [
  { id: 1, name: 'John Doe', role: 'Admin' },
  { id: 2, name: 'Joe Smith', role: 'Editor' },
  { id: 3, name: 'Alice Brown', role: 'Viewer' },
];

<Select
  label="User"
  data={users}
  def={{ valueKey: 'id', displayKey: 'name', placeholder: 'Pick a user...' }}
  width={50}
/>
```

**Custom Item Display**

```jsx
<Select
  data={users}
  def={{
    valueKey: 'id',
    display: (user) => `${user.name} — ${user.role}`,
    placeholder: 'Pick a user...',
  }}
/>
```

**Multiple with Search**

```jsx
<Select
  data={users}
  def={{
    valueKey: 'id',
    displayKey: 'name',
    placeholder: 'Pick users...',
    selectAllText: 'Select all',
    emptyText: 'No users found',
    selectedDisplay: (rows) =>
      rows.length === 0 ? 'Pick users...' : `${rows.length} selected`,
  }}
  label="Users" multiple showCheckbox isSearchable searchPlaceholder="Search users..."
/>
```

## Dropdown props

Everything below is this component’s own. All 235 of Box’s style props work on it too, and those are on [/box](https://www.box-kite.dev/box.md) rather than repeated here.

| Prop | Type | Default | What it does |
| --- | --- | --- | --- |
| `name` | `string` | — | What the selection submits under: the component renders a hidden input carrying it. |
| `defaultValue` | `TVal \| TVal[]` | — | What is selected to begin with, when the dropdown owns its own value. An array in `multiple` mode. |
| `value` | `TVal \| TVal[]` | — | Controlled selection. Pair it with `onValueChange`, or nothing the user picks sticks. |
| `defaultOpen` | `boolean` | — | Open the listbox as soon as the dropdown mounts, highlighting the selected option the way a press on the trigger would. For a dropdown that only exists because the user asked to choose — a grid's cell editor — where a second press to open a list that is the whole control is a gesture nobody meant. |
| `multiple` | `boolean` | `false` | Let more than one option be chosen. Enter and Space then toggle without closing the listbox. |
| `isSearchable` | `boolean` | — | Turn the control into the APG *editable* combobox: a text field that filters the listbox, and a different pattern rather than a decoration — the field carries the role, the ARIA and your `props`. Printable keys type instead of jumping, Home/End and left/right move the caret, and Escape closes the listbox before it clears the field. |
| `searchPlaceholder` | `string` | — | The placeholder for the `isSearchable` field. Not a name — see `label`. |
| `hideIcon` | `boolean` | — | Drop the chevron. It is decoration either way: it is `aria-hidden` and never focusable. |
| `showCheckbox` | `boolean` | `false` | Show checkbox for each item in multiple selection mode |
| `label` | `React.ReactNode` | — | The dropdown's name, rendered above it and pointed at by `aria-labelledby`. A combobox is not named by what it contains — accname does not read a combobox at all, and the trigger text is its *value* — so without this (or your own `aria-label`) it has no accessible name. |
| `labelProps` | `BoxProps<'div'>` | — | Styles for the wrapper the label and the trigger share. Only rendered when there is a label. |
| `itemsProps` | `BoxStyleProps` | — | BoxProps applied to the opened items container (dropdown.items) |
| `iconProps` | `BoxStyleProps` | — | BoxProps applied to the chevron icon container (dropdown.icon) |
| `onValueChange` | `ChangeHandler<TVal \| TVal[] \| undefined, DropdownValueReason>` | — | Fires with the selection in whatever shape `value` takes — the whole array in `multiple` mode, the one chosen option otherwise, `undefined` for nothing — and why it changed. |
| `onChange` | `(value: TVal \| undefined, values: TVal[]) => void` | — | The older callback, whose first argument is the option *acted on* rather than the selection. Both fire, so either one can be passed. |

### Dropdown.Display

| Prop | Type | Default | What it does |
| --- | --- | --- | --- |
| `children`required | `((selectedValues: TVal[], isOpen: boolean) => React.ReactNode) \| React.ReactNode` | — | What the trigger shows: a node, or a function given the selected values and whether it is open. |

## Dropdown keyboard

| Key | Result |
| --- | --- |
| `Down / Up (closed)` | Opens, with the highlight on the selected option — or on the first or last when nothing is chosen. |
| `Alt + Down (closed)` | Opens without moving the highlight. |
| `Enter / Space (closed)` | Opens. The browser's own activation is suppressed, so it does not shut again. |
| `Home / End (closed)` | Opens at the first or the last option. |
| `A printable character` | Opens with the first option starting with it already highlighted. |
| `Down / Up (open)` | Moves the highlight, wrapping at the ends and skipping disabled options. |
| `Home / End (open)` | Jumps to the first or last option. |
| `Typing (open)` | Typeahead. A longer buffer narrows; the same letter twice cycles through the options sharing it. |
| `Enter / Space (open)` | Chooses the highlighted option and closes. In `multiple` mode it toggles and stays open. |
| `Alt + Up (open)` | Chooses the highlighted option and closes. |
| `Escape` | Closes, changing nothing. Focus never left the trigger, so nothing is restored. |
| `Tab (open)` | Chooses the highlighted option, then moves on to the next control. |

### With isSearchable

| Key | Result |
| --- | --- |
| `A printable character` | Types into the field, which opens the listbox and filters it. No typeahead — the field owns the keys. |
| `Down / Up` | Opens, or moves the highlight through what the filter left, wrapping and skipping disabled options. |
| `Home / End, Left / Right` | Move the caret, and hand the highlight back to the field: no option is where you are any more. |
| `Space` | Types a space. Only Enter chooses in this mode. |
| `Enter` | Chooses the highlighted option and puts its text in the field. With nothing highlighted it does nothing. |
| `Escape` | Closes the listbox, keeping what was typed. Pressed again on a closed one, it clears the field. |

## Dropdown accessibility

Implements [the WAI-ARIA APG pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/).

- `role="combobox"` with `aria-expanded`, `aria-controls` and `aria-activedescendant` on the trigger, and `role="listbox"`/`role="option"` with `aria-selected` on the popup.
- Nothing names a combobox for you — accname does not read its contents, and the trigger text is its *value* — so pass `label`, or an `aria-label` of your own in `props`.
- DOM focus never leaves the trigger (or, when searchable, the field), so no option is a tab stop and the keyboard is never stranded in the popup. The popup itself is a sibling of the trigger in the browser's top layer, so it needs no portal and keeps the theme and the direction around it.

Swept with axe on every release, in 3 states: `Dropdown (closed)`, `Dropdown (open)`, `Dropdown (searchable, open)`. No violations, with contrast and landmark rules left to a human. Screen-reader results are not published yet.

## Dropdown style tree

Every part the component draws is a node with a name, so a default can be restyled with `Box.components()` instead of a selector — and a variant is a name too.

`dropdown`variants: compact

`dropdown.items`variants: closed, closedUp

`dropdown.item`variants: highlighted, compact, multiple

`dropdown.unselect`variants: highlighted, compact

`dropdown.selectAll`variants: highlighted, compact

`dropdown.emptyItem`variants: compact

`dropdown.icon`

---

_Every page: https://www.box-kite.dev/llms.txt · every prop, measured: https://www.box-kite.dev/props.md_

