# Dropdown

A button that opens a list of options to choose from, like a native select. Use it for one or more choices from a known list; add search when the list is long.

```tsx
// demo.tsx
import {Dropdown, Stack} from '@earendil-works/elanor-react';

const projects = ['Website redesign', 'Design system', 'Release notes'];

export default function DropdownDemo() {
  return (
    <Dropdown.Root>
      <Stack gap="small">
        <Dropdown.Label>Project</Dropdown.Label>
        <Dropdown.Trigger>
          <Dropdown.Value placeholder="Select a project" />
          <Dropdown.Icon />
        </Dropdown.Trigger>
      </Stack>
      <Dropdown.Portal>
        <Dropdown.Positioner>
          <Dropdown.Popup>
            <Dropdown.List>
              {projects.map((project) => (
                <Dropdown.Item key={project} value={project}>
                  {project}
                </Dropdown.Item>
              ))}
            </Dropdown.List>
          </Dropdown.Popup>
        </Dropdown.Positioner>
      </Dropdown.Portal>
    </Dropdown.Root>
  );
}
```

## Usage

```tsx
import {Dropdown} from '@earendil-works/elanor-react';

<Dropdown.Root>
  <Dropdown.Label />
  <Dropdown.Trigger>
    <Dropdown.Value />
    <Dropdown.Icon />
  </Dropdown.Trigger>
  <Dropdown.Portal>
    <Dropdown.Positioner>
      <Dropdown.Popup>
        <Dropdown.List>
          <Dropdown.Item />
        </Dropdown.List>
      </Dropdown.Popup>
    </Dropdown.Positioner>
  </Dropdown.Portal>
</Dropdown.Root>;
```

`Value` shows the selected value. When values differ from their labels, pass `items` to Root, such as `items={[{value: 'alpha', label: 'Alpha'}]}`.

The trigger stays on one line and truncates long values. Wrap its text in `Value` or an element, such as a `span`; bare text doesn't truncate and hides the icon.

Name the dropdown with `Label`, or with [`Prefix`](#prefix) inside the trigger. Without either, set `aria-label` on the trigger and include the value.

The popup opens below the trigger, aligned to its start edge, and repositions when space is limited. Set `alignItemWithTrigger` on Positioner to overlap the trigger with the selected item instead.

## Density

Use `compact` on the trigger in dense layouts, such as a filter beside a heading. The touch target keeps the default height, and on touch screens extends half the medium gap above and below. `SearchableDropdown.Trigger` takes the same `density`.

```tsx
// density.tsx
import {Dropdown, Stack} from '@earendil-works/elanor-react';

const ranges = [
  {value: 'week', label: 'Last 7 days'},
  {value: 'month', label: 'Last 30 days'},
  {value: 'quarter', label: 'Last 90 days'},
];

export default function DropdownDensity() {
  return (
    <Stack direction="row" gap="large" align="end" wrap="wrap">
      {(['default', 'compact'] as const).map((density) => (
        <Dropdown.Root key={density} items={ranges} defaultValue="month">
          <Stack gap="small">
            <Dropdown.Label>{density === 'default' ? 'Default' : 'Compact'}</Dropdown.Label>
            <Dropdown.Trigger density={density}>
              <Dropdown.Value />
              <Dropdown.Icon />
            </Dropdown.Trigger>
          </Stack>
          <Dropdown.Portal>
            <Dropdown.Positioner>
              <Dropdown.Popup>
                <Dropdown.List>
                  {ranges.map((range) => (
                    <Dropdown.Item key={range.value} value={range.value}>
                      <Dropdown.ItemIndicator />
                      <Dropdown.ItemText>{range.label}</Dropdown.ItemText>
                    </Dropdown.Item>
                  ))}
                </Dropdown.List>
              </Dropdown.Popup>
            </Dropdown.Positioner>
          </Dropdown.Portal>
        </Dropdown.Root>
      ))}
    </Stack>
  );
}
```

## Prefix

`Prefix` labels the dropdown inside the trigger, such as "Date range: Last 30 days". Use it when there's no room for `Label`, and keep it to a word or two. Prefix adds the colon, so leave it out of the text. Only the value truncates.

Use Prefix instead of `Label`, or instead of `Field.Label` inside a `Field`. It overrides `aria-label` on the trigger.

```tsx
// prefix.tsx
import {Dropdown} from '@earendil-works/elanor-react';

const ranges = [
  {value: 'week', label: 'Last 7 days'},
  {value: 'month', label: 'Last 30 days'},
  {value: 'quarter', label: 'Last 90 days'},
];

export default function DropdownPrefix() {
  return (
    <Dropdown.Root items={ranges} defaultValue="month">
      <Dropdown.Trigger density="compact">
        <Dropdown.Prefix>Date range</Dropdown.Prefix>
        <Dropdown.Value />
        <Dropdown.Icon />
      </Dropdown.Trigger>
      <Dropdown.Portal>
        <Dropdown.Positioner>
          <Dropdown.Popup>
            <Dropdown.List>
              {ranges.map((range) => (
                <Dropdown.Item key={range.value} value={range.value}>
                  <Dropdown.ItemIndicator />
                  <Dropdown.ItemText>{range.label}</Dropdown.ItemText>
                </Dropdown.Item>
              ))}
            </Dropdown.List>
          </Dropdown.Popup>
        </Dropdown.Positioner>
      </Dropdown.Portal>
    </Dropdown.Root>
  );
}
```

## Clear

`Clear` is a button after the trigger that clears the value and moves focus back to the trigger. It renders only while there's something to clear. Put the trigger and Clear in `Control`, which joins them in one fill; Clear is the next tab stop after the trigger.

Use Clear on filters, where no value means no filter, not on view controls that always have a value, such as a date range or a sort order.

Name Clear with `aria-label`, such as “Clear Status filter”, and show the empty value with `Value`'s `placeholder`, such as “All”. Clear calls `onValueChange` with `null`, or `[]` with `multiple`, and the reason `clear-press`. A disabled or read-only Root, or a disabled `Field` or `Fieldset`, disables it. Its touch target matches the trigger's.

When you control Root, pass `null` for no value, or `[]` with `multiple`, such as `value={search.status ?? null}`. `undefined` makes Root uncontrolled.

```tsx
// clear.tsx
import {Dropdown, Grid} from '@earendil-works/elanor-react';

const statuses = [
  {value: 'active', label: 'Active'},
  {value: 'invited', label: 'Invited'},
  {value: 'suspended', label: 'Suspended'},
];

export default function DropdownClear() {
  return (
    <Grid.Root>
      <Grid.Item span={{base: 'full', xSmall: 4}}>
        <Dropdown.Root items={statuses} defaultValue="invited">
          <Dropdown.Control>
            <Dropdown.Trigger density="compact">
              <Dropdown.Prefix>Status</Dropdown.Prefix>
              <Dropdown.Value placeholder="All" />
              <Dropdown.Icon />
            </Dropdown.Trigger>
            <Dropdown.Clear aria-label="Clear Status filter" />
          </Dropdown.Control>
          <Dropdown.Portal>
            <Dropdown.Positioner>
              <Dropdown.Popup>
                <Dropdown.List>
                  {statuses.map((status) => (
                    <Dropdown.Item key={status.value} value={status.value}>
                      <Dropdown.ItemIndicator />
                      <Dropdown.ItemText>{status.label}</Dropdown.ItemText>
                    </Dropdown.Item>
                  ))}
                </Dropdown.List>
              </Dropdown.Popup>
            </Dropdown.Positioner>
          </Dropdown.Portal>
        </Dropdown.Root>
      </Grid.Item>
    </Grid.Root>
  );
}
```

With `multiple`, Clear empties the array.

```tsx
// clear-multiple.tsx
import {Dropdown, Grid} from '@earendil-works/elanor-react';

const groups = ['developers', 'design', 'operations', 'support'];

export default function DropdownClearMultiple() {
  return (
    <Grid.Root>
      <Grid.Item span={{base: 'full', xSmall: 4}}>
        <Dropdown.Root multiple defaultValue={['developers', 'design', 'support']}>
          <Dropdown.Control>
            <Dropdown.Trigger density="compact">
              <Dropdown.Prefix>Groups</Dropdown.Prefix>
              <Dropdown.Value placeholder="All" />
              <Dropdown.Icon />
            </Dropdown.Trigger>
            <Dropdown.Clear aria-label="Clear Groups filter" />
          </Dropdown.Control>
          <Dropdown.Portal>
            <Dropdown.Positioner>
              <Dropdown.Popup>
                <Dropdown.List>
                  {groups.map((group) => (
                    <Dropdown.Item key={group} value={group}>
                      <Dropdown.ItemIndicator />
                      <Dropdown.ItemText>{group}</Dropdown.ItemText>
                    </Dropdown.Item>
                  ))}
                </Dropdown.List>
              </Dropdown.Popup>
            </Dropdown.Positioner>
          </Dropdown.Portal>
        </Dropdown.Root>
      </Grid.Item>
    </Grid.Root>
  );
}
```

### Clear to a default

For a filter that starts at a value, such as API keys that show only active ones, pass the default to Root as `clearValue`. Clear then shows while the value differs from it, including when nothing is selected, and sets it back. Name it for what it does, such as “Reset Status filter to Active”.

```tsx
// clear-default.tsx
import {Dropdown, Grid} from '@earendil-works/elanor-react';
import {useState} from 'react';

const statuses = [
  {value: 'active', label: 'Active'},
  {value: 'expired', label: 'Expired'},
  {value: 'revoked', label: 'Revoked'},
];
const defaultStatus = ['active'];

export default function DropdownClearDefault() {
  // Unchecking every status shows all keys; Clear goes back to the default.
  const [status, setStatus] = useState(defaultStatus);

  return (
    <Grid.Root>
      <Grid.Item span={{base: 'full', xSmall: 4}}>
        <Dropdown.Root
          multiple
          items={statuses}
          value={status}
          clearValue={defaultStatus}
          onValueChange={setStatus}
        >
          <Dropdown.Control>
            <Dropdown.Trigger density="compact">
              <Dropdown.Prefix>Status</Dropdown.Prefix>
              <Dropdown.Value placeholder="All" />
              <Dropdown.Icon />
            </Dropdown.Trigger>
            <Dropdown.Clear aria-label="Reset Status filter to Active" />
          </Dropdown.Control>
          <Dropdown.Portal>
            <Dropdown.Positioner>
              <Dropdown.Popup>
                <Dropdown.List>
                  {statuses.map((option) => (
                    <Dropdown.Item key={option.value} value={option.value}>
                      <Dropdown.ItemIndicator />
                      <Dropdown.ItemText>{option.label}</Dropdown.ItemText>
                    </Dropdown.Item>
                  ))}
                </Dropdown.List>
              </Dropdown.Popup>
            </Dropdown.Positioner>
          </Dropdown.Portal>
        </Dropdown.Root>
      </Grid.Item>
    </Grid.Root>
  );
}
```

## Value count

`ValueCount` shows how many more values are selected, such as “Groups: developers +2”. Render the first selected value with `Value`'s children and put ValueCount after it. It counts from Root unless you pass `count`, and stays whole while the value truncates. Screen readers read “developers and 2 more”; pass `label` to translate it.

```tsx
// value-count.tsx
import {Dropdown, Grid} from '@earendil-works/elanor-react';

const groups = ['developers', 'design', 'operations', 'support'];

export default function DropdownValueCount() {
  return (
    <Grid.Root>
      <Grid.Item span={{base: 'full', xSmall: 4}}>
        <Dropdown.Root multiple defaultValue={['developers', 'design', 'support']}>
          <Dropdown.Trigger density="compact">
            <Dropdown.Prefix>Groups</Dropdown.Prefix>
            <Dropdown.Value>{(values: string[]) => values[0] ?? 'All'}</Dropdown.Value>
            <Dropdown.ValueCount />
            <Dropdown.Icon />
          </Dropdown.Trigger>
          <Dropdown.Portal>
            <Dropdown.Positioner>
              <Dropdown.Popup>
                <Dropdown.List>
                  {groups.map((group) => (
                    <Dropdown.Item key={group} value={group}>
                      <Dropdown.ItemIndicator />
                      <Dropdown.ItemText>{group}</Dropdown.ItemText>
                    </Dropdown.Item>
                  ))}
                </Dropdown.List>
              </Dropdown.Popup>
            </Dropdown.Positioner>
          </Dropdown.Portal>
        </Dropdown.Root>
      </Grid.Item>
    </Grid.Root>
  );
}
```

## Item indicator

`ItemIndicator` marks the selected item. Put the label next to it in `ItemText`.

```tsx
// item-indicator.tsx
import {Dropdown, Stack} from '@earendil-works/elanor-react';

const projects = ['Website redesign', 'Design system', 'Release notes'];

export default function DropdownItemIndicator() {
  return (
    <Dropdown.Root defaultValue="Design system">
      <Stack gap="small">
        <Dropdown.Label>Project</Dropdown.Label>
        <Dropdown.Trigger>
          <Dropdown.Value />
          <Dropdown.Icon />
        </Dropdown.Trigger>
      </Stack>
      <Dropdown.Portal>
        <Dropdown.Positioner>
          <Dropdown.Popup>
            <Dropdown.List>
              {projects.map((project) => (
                <Dropdown.Item key={project} value={project}>
                  <Dropdown.ItemIndicator />
                  <Dropdown.ItemText>{project}</Dropdown.ItemText>
                </Dropdown.Item>
              ))}
            </Dropdown.List>
          </Dropdown.Popup>
        </Dropdown.Positioner>
      </Dropdown.Portal>
    </Dropdown.Root>
  );
}
```

## Multiple selection

With `multiple`, the value is an array and `ItemIndicator` shows a checkbox.

```tsx
// multiple.tsx
import {Dropdown, Stack} from '@earendil-works/elanor-react';

const projects = ['Website redesign', 'Design system', 'Release notes'];

export default function DropdownMultiple() {
  return (
    <Dropdown.Root multiple defaultValue={['Design system']}>
      <Stack gap="small">
        <Dropdown.Label>Projects</Dropdown.Label>
        <Dropdown.Trigger>
          <Dropdown.Value placeholder="Select projects" />
          <Dropdown.Icon />
        </Dropdown.Trigger>
      </Stack>
      <Dropdown.Portal>
        <Dropdown.Positioner>
          <Dropdown.Popup>
            <Dropdown.List>
              {projects.map((project) => (
                <Dropdown.Item key={project} value={project}>
                  <Dropdown.ItemIndicator />
                  <Dropdown.ItemText>{project}</Dropdown.ItemText>
                </Dropdown.Item>
              ))}
            </Dropdown.List>
          </Dropdown.Popup>
        </Dropdown.Positioner>
      </Dropdown.Portal>
    </Dropdown.Root>
  );
}
```

## Groups

`Group`, `GroupLabel`, and `Separator` divide long lists.

```tsx
// groups.tsx
import {Dropdown, Stack} from '@earendil-works/elanor-react';

const regions = {
  Europe: ['Frankfurt', 'London'],
  'North America': ['Toronto', 'Virginia'],
};

export default function DropdownGroups() {
  return (
    <Dropdown.Root defaultValue="Frankfurt">
      <Stack gap="small">
        <Dropdown.Label>Region</Dropdown.Label>
        <Dropdown.Trigger>
          <Dropdown.Value />
          <Dropdown.Icon />
        </Dropdown.Trigger>
      </Stack>
      <Dropdown.Portal>
        <Dropdown.Positioner>
          <Dropdown.Popup>
            <Dropdown.List>
              {Object.entries(regions).map(([continent, cities], index) => (
                <Dropdown.Group key={continent}>
                  {index > 0 ? <Dropdown.Separator /> : null}
                  <Dropdown.GroupLabel>{continent}</Dropdown.GroupLabel>
                  {cities.map((city) => (
                    <Dropdown.Item key={city} value={city}>
                      {city}
                    </Dropdown.Item>
                  ))}
                </Dropdown.Group>
              ))}
            </Dropdown.List>
          </Dropdown.Popup>
        </Dropdown.Positioner>
      </Dropdown.Portal>
    </Dropdown.Root>
  );
}
```

## Searchable

`SearchableDropdown` adds a filter input to the popup, aligned like Dropdown's. Keep `Status` outside the popup so screen readers announce the selection after it closes. For a list that is always visible, see [Searchable list](/components/searchable-list.md).

```tsx
// searchable.tsx
import {SearchableDropdown, Stack} from '@earendil-works/elanor-react';
import {useState} from 'react';

const projects = ['Website redesign', 'Design system', 'Release notes', 'Help center'];

export default function DropdownSearchable() {
  const [project, setProject] = useState<string | null>(null);

  return (
    <SearchableDropdown.Root items={projects} value={project} onValueChange={setProject}>
      <Stack gap="small">
        <SearchableDropdown.Label>Project</SearchableDropdown.Label>
        <SearchableDropdown.Trigger>
          <SearchableDropdown.Value placeholder="Select a project" />
          <SearchableDropdown.Icon />
        </SearchableDropdown.Trigger>
      </Stack>
      <SearchableDropdown.Portal>
        <SearchableDropdown.Positioner>
          <SearchableDropdown.Popup aria-label="Choose a project">
            <SearchableDropdown.InputGroup>
              <SearchableDropdown.Input
                aria-label="Filter projects"
                placeholder="Filter projects"
              />
            </SearchableDropdown.InputGroup>
            <SearchableDropdown.Empty>No projects found.</SearchableDropdown.Empty>
            <SearchableDropdown.List>
              {(item: string) => (
                <SearchableDropdown.Item key={item} value={item}>
                  <SearchableDropdown.ItemIndicator />
                  {item}
                </SearchableDropdown.Item>
              )}
            </SearchableDropdown.List>
          </SearchableDropdown.Popup>
        </SearchableDropdown.Positioner>
      </SearchableDropdown.Portal>
      <SearchableDropdown.Status>
        {project === null ? null : `Selected ${project}.`}
      </SearchableDropdown.Status>
    </SearchableDropdown.Root>
  );
}
```

`SearchableDropdown` has the same `Prefix`, `ValueCount`, `Control` and `Clear`. Its `Value` renders no element, so wrap it in a `span` to truncate it.

```tsx
// searchable-filter.tsx
import {Grid, SearchableDropdown} from '@earendil-works/elanor-react';
import {useState} from 'react';

const groups = ['developers', 'design', 'operations', 'support', 'marketing', 'finance'];

export default function DropdownSearchableFilter() {
  const [selected, setSelected] = useState(['developers', 'operations']);

  return (
    <Grid.Root>
      <Grid.Item span={{base: 'full', xSmall: 4}}>
        <SearchableDropdown.Root
          items={groups}
          multiple
          value={selected}
          onValueChange={setSelected}
        >
          <SearchableDropdown.Control>
            <SearchableDropdown.Trigger density="compact">
              <SearchableDropdown.Prefix>Groups</SearchableDropdown.Prefix>
              <span>
                <SearchableDropdown.Value>
                  {(values: string[]) => values[0] ?? 'All'}
                </SearchableDropdown.Value>
              </span>
              <SearchableDropdown.ValueCount />
              <SearchableDropdown.Icon />
            </SearchableDropdown.Trigger>
            <SearchableDropdown.Clear aria-label="Clear Groups filter" />
          </SearchableDropdown.Control>
          <SearchableDropdown.Portal>
            <SearchableDropdown.Positioner>
              <SearchableDropdown.Popup aria-label="Choose groups">
                <SearchableDropdown.InputGroup>
                  <SearchableDropdown.Input
                    aria-label="Search groups"
                    placeholder="Search groups"
                  />
                </SearchableDropdown.InputGroup>
                <SearchableDropdown.Empty>No matching groups.</SearchableDropdown.Empty>
                <SearchableDropdown.List>
                  {(group: string) => (
                    <SearchableDropdown.Item key={group} value={group}>
                      <SearchableDropdown.ItemIndicator />
                      {group}
                    </SearchableDropdown.Item>
                  )}
                </SearchableDropdown.List>
              </SearchableDropdown.Popup>
            </SearchableDropdown.Positioner>
          </SearchableDropdown.Portal>
        </SearchableDropdown.Root>
      </Grid.Item>
    </Grid.Root>
  );
}
```

## API

### `Dropdown.Root`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `onValueChange` | `\| (( value: DropdownRootChangedValue<Value, Multiple>, eventDetails: DropdownRootChangeEventDetails, ) => void)` | - | Called when the value changes. When Clear clears it, the value is `clearValue` and the reason is `clear-press`. |
| `clearValue` | `DropdownRootChangedValue<Value, Multiple>` | `null, or [] with `multiple`` | The value Clear sets, such as a filter's default. Clear shows while the value differs from it. Compared as Base UI compares items, with `isItemEqualToValue`. |

Also accepts Base UI [`Select.Root`](https://base-ui.com/react/components/select.md) props.

### `Dropdown.Label`

Accepts Base UI [`Select.Label`](https://base-ui.com/react/components/select.md) props.

### `Dropdown.Trigger`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `density` | `'compact' \| 'default'` | `'default'` | `compact` removes block padding but keeps the touch target height. |

Also accepts Base UI [`Select.Trigger`](https://base-ui.com/react/components/select.md) props.

### `Dropdown.Value`

Accepts Base UI [`Select.Value`](https://base-ui.com/react/components/select.md) props.

### `Dropdown.Icon`

Accepts Base UI [`Select.Icon`](https://base-ui.com/react/components/select.md) props.

### `Dropdown.Portal`

Accepts Base UI [`Select.Portal`](https://base-ui.com/react/components/select.md) props.

### `Dropdown.Positioner`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `alignItemWithTrigger` | `boolean` | `false` | Whether the positioner overlaps the trigger so the selected item's text is aligned with the trigger's value text. |
| `align` | `Align` | `'start'` | How to align the popup relative to the specified side. |

Also accepts Base UI [`Select.Positioner`](https://base-ui.com/react/components/select.md) props.

### `Dropdown.Popup`

Accepts Base UI [`Select.Popup`](https://base-ui.com/react/components/select.md) props.

### `Dropdown.List`

Accepts Base UI [`Select.List`](https://base-ui.com/react/components/select.md) props.

### `Dropdown.Item`

Accepts Base UI [`Select.Item`](https://base-ui.com/react/components/select.md) props.

### `Dropdown.Prefix`

A label inside the trigger, before the value. Use it instead of `Label`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `onPointerDown` | `PointerEventHandler<HTMLSpanElement>` | - | A plain React handler; it replaces Base UI's label handler. |

Also accepts Base UI [`Select`](https://base-ui.com/react/components/select.md) props.

### `Dropdown.ItemIndicator`

Accepts Base UI [`Select.ItemIndicator`](https://base-ui.com/react/components/select.md) props.

### `Dropdown.GroupLabel`

Accepts Base UI [`Select.GroupLabel`](https://base-ui.com/react/components/select.md) props.

### `Dropdown.Separator`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation` | `'horizontal'` | - | Only horizontal is supported. |

Also accepts Base UI [`Select.Separator`](https://base-ui.com/react/components/select.md) props.

### `Dropdown.Control`

Joins the trigger and Clear in one fill. `data-has-value` is set while a value is selected.

Renders `<div>`. Accepts its HTML attributes and `ref`.

### `Dropdown.Clear`

Clears the value, to none or Root's `clearValue`, and focuses the trigger. Renders only while there's something to clear. Put it after the trigger, inside Control.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `aria-label` (required) | `string` | - | Names the button, such as “Clear Status filter”. |
| `type` | `'submit' \| 'reset' \| 'button'` | `'button'` |  |

Also accepts Base UI [`Button`](https://base-ui.com/react/components/button.md) props.

### `Dropdown.ValueCount`

A muted count of the selected values after the first, such as “+2”.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `count` | `number` | `the selected values after the first` | How many values to count, shown as “+2”. Nothing renders below 1. |
| `label` | `((count: number) => string)` | `(count) => `and ${count} more`` | What screen readers read instead of “+2”, such as “and 2 more”. |

Renders `<span>`. Also accepts its HTML attributes and `ref`.

### `Dropdown.ItemText`

Accepts Base UI [`Select.ItemText`](https://base-ui.com/react/components/select.md) props.

### `Dropdown.Group`

Accepts Base UI [`Select.Group`](https://base-ui.com/react/components/select.md) props.

### `SearchableDropdown.Root`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `grid` | `false` | - | Only list navigation is supported; grid layouts are not. |
| `clearValue` | `SearchableDropdownRootChangedValue<Value, Multiple>` | `null, or [] with `multiple`` | The value Clear sets, such as a filter's default. Clear shows while the value differs from it. Compared as Base UI compares items, with `isItemEqualToValue`. |

Also accepts Base UI [`Combobox.Root`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.Label`

Accepts Base UI [`Combobox.Label`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.Prefix`

A label inside the trigger, before the value. Use it instead of `Label`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `onPointerDown` | `PointerEventHandler<HTMLSpanElement>` | - | A plain React handler; it replaces Base UI's label handler. |

Also accepts Base UI [`Combobox`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.Trigger`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `density` | `'compact' \| 'default'` | `'default'` | `compact` removes block padding but keeps the touch target height. |

Also accepts Base UI [`Combobox.Trigger`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.Icon`

Accepts Base UI [`Combobox.Icon`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.Positioner`

Places the popup below the trigger, aligned to its start edge, as Dropdown's.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `Align` | `'start'` | How to align the popup relative to the specified side. |

Also accepts Base UI [`Combobox.Positioner`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.Popup`

Accepts Base UI [`Combobox.Popup`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.InputGroup`

Accepts Base UI [`Combobox.InputGroup`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.Input`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `type` | `'search' \| 'text'` | `'text'` |  |

Also accepts Base UI [`Combobox.Input`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.List`

Accepts Base UI [`Combobox.List`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.Item`

Accepts Base UI [`Combobox.Item`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.ItemIndicator`

Accepts Base UI [`Combobox.ItemIndicator`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.GroupLabel`

Accepts Base UI [`Combobox.GroupLabel`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.Separator`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation` | `'horizontal'` | - | Only horizontal is supported. |

Also accepts Base UI [`Combobox.Separator`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.Empty`

Accepts Base UI [`Combobox.Empty`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.Status`

Accepts Base UI [`Combobox.Status`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.Control`

Joins the trigger and Clear in one fill. `data-has-value` is set while a value is selected.

Renders `<div>`. Accepts its HTML attributes and `ref`.

### `SearchableDropdown.Clear`

Clears the value, to none or Root's `clearValue`, and focuses the trigger. Renders only while there's something to clear. Put it after the trigger, inside Control.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `aria-label` (required) | `string` | - | Names the button, such as “Clear Status filter”. |
| `type` | `'submit' \| 'reset' \| 'button'` | `'button'` |  |

Also accepts Base UI [`Button`](https://base-ui.com/react/components/button.md) props.

### `SearchableDropdown.ValueCount`

A muted count of the selected values after the first, such as “+2”.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `count` | `number` | `the selected values after the first` | How many values to count, shown as “+2”. Nothing renders below 1. |
| `label` | `((count: number) => string)` | `(count) => `and ${count} more`` | What screen readers read instead of “+2”, such as “and 2 more”. |

Renders `<span>`. Also accepts its HTML attributes and `ref`.

### `SearchableDropdown.Value`

Accepts Base UI [`Combobox.Value`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.Portal`

Accepts Base UI [`Combobox.Portal`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.Group`

Accepts Base UI [`Combobox.Group`](https://base-ui.com/react/components/combobox.md) props.

### `SearchableDropdown.Collection`

Accepts Base UI [`Combobox.Collection`](https://base-ui.com/react/components/combobox.md) props.
