# ComboboxList (/docs/combobox-list)





`ComboboxList` is [`Select`](/docs/select)'s data contract without the trigger: a search box and an option list that render **permanently open**, for embedding inside a container you already own the visibility of (a [`Popover`](/docs/popover), a [`Drawer`](/docs/drawer), a custom panel) instead of owning its own floating dropdown. It's what powers `DataTable`'s [select column filter](/docs/data-table#column-filters) — click the filter icon once and you're already looking at a focused search box and list, no nested trigger to open.

```tsx
'use client';
import { ComboboxList } from '@42-central/ui-react/combobox-list';

<ComboboxList data={['Paris', 'Lyon', 'Nice']} />
```

For a self-contained trigger + floating dropdown, use [`Select`](/docs/select) instead — same `data` / `value` / `onChange` contract, just with its own closed/open trigger button. For the multi-value equivalent see [`MultiComboboxList`](/docs/multi-combobox-list).

The playground below renders the list bare, on the page, so you can see it clearly — in real use it almost always sits inside something else that owns the open/close state:

```tsx
'use client';
import { Popover } from '@42-central/ui-react/popover';
import { ComboboxList } from '@42-central/ui-react/combobox-list';

<Popover.Root>
  <Popover.Trigger>Filter</Popover.Trigger>
  <Popover.Content>
    <Popover.Body>
      <ComboboxList data={['Paris', 'Lyon', 'Nice']} />
    </Popover.Body>
  </Popover.Content>
</Popover.Root>
```

## Playground [#playground]

<story.Playground />

## Data shapes [#data-shapes]

`data` accepts the same three shapes as `Select` — strings, `{ value, label?, disabled? }` objects, or `{ group, items }` groups:

<story.Preview variant="Grouped" />

## Clearable [#clearable]

`clearable` adds a clear button beside the search icon once there's a value.

<story.Preview variant="Clearable" />

## Custom rendering [#custom-rendering]

`renderItem` works exactly like [`Select`'s](/docs/select#custom-rendering) — it receives the (value-narrowed) `item`, including any typed `payload`, and the live `{ selected, highlighted }` state, and returns whatever the row should show.

<story.Preview variant="Custom rendering" />

## Label, description & error [#label-description--error]

Built in via [`Field`](/docs/field), like the rest of the family — though most of the time you'll omit them here, since the container embedding this (a filter popover, a drawer) usually carries its own label.

<story.Preview variant="With label" />

## Controlled [#controlled]

`onChange` hands you the selected value, or `null` when cleared.

```tsx
'use client';
import { useState } from 'react';
import { ComboboxList } from '@42-central/ui-react/combobox-list';

export function ProjectList() {
  const [value, setValue] = useState<string | null>(null);
  return (
    <ComboboxList
      data={['Libft', 'ft_printf', 'Get Next Line']}
      value={value}
      onChange={setValue}
      clearable
    />
  );
}
```

## Forms [#forms]

A `name` renders a hidden input carrying the picked value, so it wires into native `<form>` submission and `FormData` — the search input itself is never named (it would submit the typed query, not the value).

```tsx
<form action={save}>
  <ComboboxList name="project" data={['Libft', 'ft_printf', 'Get Next Line']} />
</form>
```

## API [#api]

<TypeTable
  type="{
  data: {
    type: 'string[] | SelectItem[] | SelectGroup[]',
    description: 'Options. Strings, { value, label?, disabled? }, or { group, items }.',
  },
  value: { type: 'string | null', description: 'Controlled value.' },
  defaultValue: { type: 'string | null', description: 'Uncontrolled initial value.' },
  onChange: {
    type: '(value: string | null) => void',
    description: 'Fires with the selected value, or null when cleared.',
  },
  renderItem: {
    type: '(item, state) => ReactNode',
    description: 'Render each option yourself. item is { value, label, disabled }; state is { selected, highlighted }. Define it in a &#x22;use client&#x22; module.',
  },
  placeholder: { type: 'string', default: '&#x22;Search…&#x22;', description: 'Placeholder for the search field.' },
  empty: { type: 'ReactNode', default: '&#x22;Nothing found&#x22;', description: 'Message when no option matches. Pass null to render nothing.' },
  clearable: { type: 'boolean', default: 'false', description: 'Show a clear button once a value is set.' },
  size: { type: '&#x22;xs&#x22; | &#x22;sm&#x22; | &#x22;md&#x22; | &#x22;lg&#x22; | &#x22;xl&#x22;', default: '&#x22;md&#x22;', description: 'Search row + option sizing.' },
  variant: { type: '&#x22;default&#x22; | &#x22;unstyled&#x22;', default: '&#x22;unstyled&#x22;', description: 'Search row treatment — unstyled by default since this is normally embedded in a container that already draws its own chrome.' },
  color: { type: 'Color', default: '&#x22;brand&#x22;', description: 'Focus-ring / selected-check accent.' },
  label: { type: 'ReactNode', description: 'Field label.' },
  description: { type: 'ReactNode', description: 'Field helper text.' },
  error: { type: 'ReactNode', description: 'Field error; sets the invalid state.' },
  required: { type: 'boolean', default: 'false', description: 'Required flag + asterisk.' },
  disabled: { type: 'boolean', default: 'false', description: 'Disable the control.' },
  readOnly: { type: 'boolean', default: 'false', description: 'Announce the value but reject changes.' },
  name: { type: 'string', description: 'Name for a hidden input carrying the value (form submission).' },
  translations: {
    type: '{ clearTriggerLabel?: string }',
    description: &#x22;Accessible name of the clear button (default \&#x22;Clear value\&#x22;) — the combobox family's shared key.&#x22;,
  },
  classNames: { type: '{ field?, search?, input?, option?, list? }', description: 'Per-part class overrides.' },
  testId: { type: 'string', description: 'data-testid on the real search input. Each data item also takes its own optional testId, defaulting to `item-${value}` — see the Testing guide.' },
}"
/>

## Accessibility [#accessibility]

* Same combobox keyboard model as `Select`/`Autocomplete`: type to filter, `↑` / `↓` to move, `Enter` to pick, `Home` / `End` to jump to first / last.
* `role="combobox"` on the search input with `aria-expanded="true"` (it's permanently open), `role="option"` on each row, and the active-descendant wiring from Ark's Combobox machine.
* There's no trigger and no floating panel — accessibility for the *open/close* affordance (focus return, `Esc` handling, `aria-haspopup`) is the embedding container's job (e.g. `Popover`already provides it).
