# MultiComboboxList (/docs/multi-combobox-list)





`MultiComboboxList` is [`MultiSelect`](/docs/multi-select)'s data contract without the trigger or the pills: a search box and a checkbox 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). Picking an option toggles its checkbox without closing the list — same idea as [`ComboboxList`](/docs/combobox-list), just for several values at once. It's what powers `DataTable`'s [multi-select column filter](/docs/data-table#column-filters).

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

<MultiComboboxList data={['Libft', 'ft_printf', 'Get Next Line']} defaultValue={['Libft']} />
```

For a self-contained trigger with pills in the field, use [`MultiSelect`](/docs/multi-select) instead — same `data` / `value` / `onChange` contract. For the single-value equivalent see [`ComboboxList`](/docs/combobox-list).

The playground below renders the list bare, on the page — in real use it almost always sits inside something else that owns the open/close state (see `ComboboxList`'s [Popover example](/docs/combobox-list)).

## Playground [#playground]

<story.Playground />

## Checkboxes, not pills [#checkboxes-not-pills]

Every option shows a checkbox instead of a check mark; picking one adds it to the selection and keeps the list open so you can keep picking. `data` accepts the same shapes as `MultiSelect` — strings, objects, or groups.

<story.Preview variant="Grouped" />

## Hide picked options [#hide-picked-options]

With `hidePickedOptions`, chosen values drop out of the list so it only ever shows what's still addable.

<story.Preview variant="Hide picked" />

## Max values [#max-values]

`maxValues` caps the selection — once reached, adding more is rejected (removals still work).

<story.Preview variant="Max values" />

## Clearable [#clearable]

`clearable` adds a button that clears every value at once.

<story.Preview variant="Clearable" />

## Custom rendering [#custom-rendering]

`renderItem` owns each row's whole content — the same `(item, state) => ReactNode` renderer as [`Select`'s](/docs/select#custom-rendering), with `item` being `{ value, label, disabled }` and state `{ selected, highlighted }`. It fully replaces the default checkbox + label row, so if you still want a selected affordance, draw your own from `state.selected`.

<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]

`value` is a `string[]`; `onChange` hands you the full selection on every add or remove.

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

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

## Forms [#forms]

A `name` renders one hidden input per selected 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 selection).

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

## API [#api]

<TypeTable
  type="{
  data: { type: 'string[] | SelectItem[] | SelectGroup[]', description: 'Options. Same shapes as MultiSelect.' },
  value: { type: 'string[]', description: 'Controlled selection.' },
  defaultValue: { type: 'string[]', description: 'Uncontrolled initial selection.' },
  onChange: { type: '(value: string[]) => void', description: 'Fires with the full selection.' },
  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 button that clears every value at once.' },
  maxValues: { type: 'number', description: 'Cap the number of selected values.' },
  hidePickedOptions: { type: 'boolean', default: 'false', description: 'Hide already-selected options.' },
  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 + checkbox 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 / checked-box 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: 'Read-only (no add / remove).' },
  name: { type: 'string', description: 'Name for hidden inputs carrying the selection (form submission), one per value.' },
  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?, checkbox?, 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]

* Full combobox keyboard model from Ark: type to filter, `↑` / `↓` to move, `Enter` to toggle an option, `Esc` handled by the embedding container.
* `role="combobox"` on the search input with `aria-expanded="true"` (it is permanently open), `role="option"` with `aria-selected` on each row, `aria-multiselectable` on the list.
* 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).
