# Multi-select

> Several choices from a short list: the pill names them ("Design, Sales +2"), the list rises off it with ticks that draw, and it stays open while you pick.

Multi-selects usually fill their field with chips that wrap onto three lines, or close after every pick. This one keeps the pill to one line: it names the first choices and rolls a "+N" for the rest, while the words morph as you tick. The list rises off the pill and stays open; ticks draw themselves, a filter appears for longer lists, and Select all / Clear sit under a count that rolls.

## Install

```bash
npx shadcn@latest add https://hairlineui.com/r/multi-select.json
```

Needs React 19, Tailwind CSS v4 and shadcn/ui theme variables (any style, light and dark). Icons from lucide-react.

- npm: none
- Hairline UI components it needs (the CLI installs them too): `number-roll`, `text-morph`, `ui-interaction-motion`
- Docs and live preview: https://hairlineui.com/ui/multi-select
- Registry item (source): https://hairlineui.com/r/multi-select.json

## Usage

```tsx
import { MultiSelect } from "@/components/ui/multi-select";

<MultiSelect label="Status" defaultValue={["open"]} options={[
  { value: "open", label: "Open" },
  { value: "pending", label: "Pending" },
  { value: "closed", label: "Closed" },
]} />
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `options` | `MultiOption[]` | – | value, label, description, disabled. |
| `value / defaultValue` | `string[]` | – | Chosen values, kept in the options' order. |
| `onValueChange` | `(value: string[]) => void` | – | Called on every tick. |
| `label` | `string` | – | Shown above; accessible name. |
| `searchable` | `boolean` | `More than 7 options` | Show the filter. |
| `maxShown` | `number` | `2` | Labels named before +N. |
| `hint / error / placeholder` | `ReactNode` | – | As in Input. |

## States

- **closed**: Names of the first two choices and a +N that rolls.
- **open**: The list rises off the pill (320ms); the highlight glides between rows.
- **tick**: The box fills and its tick draws (260ms); the pill's words morph.

## What it handles

Chips are good for a few values and bad for a filter field that must stay one line; a summary with a rolling count stays put.

- **One-line summary**: First names, then a rolling +N.
- **Stays open**: Pick several; Done or Escape closes it.
- **Filter for long lists**: Appears past seven options.
- **Listbox semantics**: aria-multiselectable, aria-selected, active descendant.

## Replaces

react-select (multi), checkbox dropdown, filter menu

## Where it fits

- **Table filters**: Status: Open, Pending +1.
- **Notification settings**: Which channels to be told on.
- **Assignment**: Teams that can see a project.
- **Forms**: Interests or regions in a profile.

## Notes

- Placement, presence and dismissal come from floating.ts, shared with tooltip, popover and the menus.
- For long or remote lists searched first, use combobox.
- Reduced motion: the list and ticks change in place.

License: MIT (https://hairlineui.com/license#components)
