Multi-select

Select several values while keeping the field readable.

fieldchoice
Preview
Multi-select preview
Skills

When to use

  • Picking several values from a fixed list in a compact field, such as labels or assignees.
  • Filters where the chosen values should show in the field with a +N overflow.

When not to use

  • Use chip-group when every option should stay visible.
  • Use tag-input for free-form values.
  • Use select for a single value.

shadcn CLI

Also installs (1)

  • manicat-foundation
Manual install(+)
registry.jsonjson
{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "multi-select",
  "type": "registry:ui",
  "title": "Multi-select",
  "description": "Select several values while keeping the field readable.",
  "categories": [
    "inputs"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "https://manicat.viraza.net/r/manicat-foundation.json"
  ],
  "files": [
    {
      "path": "registry/components/multi-select/multi-select.tsx",
      "type": "registry:component",
      "target": "@components/manicat/multi-select/multi-select.tsx"
    },
    {
      "path": "registry/components/multi-select/multi-select.module.css",
      "type": "registry:component",
      "target": "@components/manicat/multi-select/multi-select.module.css"
    }
  ],
  "docs": "Docs and live preview: https://uiarc.dev/components/multi-select",
  "meta": {
    "tier": "free",
    "kind": "component",
    "docs": "https://manicat.viraza.net/components/multi-select",
    "markdown": "https://uiarc.dev/components/multi-select/markdown",
    "tags": [
      "field",
      "choice"
    ]
  }
}

Usage

Install the component, then copy this into your app. Change the content and the props to fit your screen.

example.tsxtsx
"use client";

import { useState } from "react";
import { MultiSelect } from "@/components/manicat/multi-select/multi-select";

export function MultiSelectExample() {
  const [value, setValue] = useState(["design", "motion"]);

  return (
    <MultiSelect
      label="Skills"
      value={value}
      onValueChange={setValue}
      maxVisible={2}
      options={[
        { value: "design", label: "Design" },
        { value: "motion", label: "Motion" },
        { value: "data", label: "Data visualisation" },
        { value: "a11y", label: "Accessibility" },
        { value: "i18n", label: "Internationalisation" },
      ]}
    />
  );
}

Examples

Real screens rather than isolated props. Switch to Code to copy a whole example into your project.

Skills Multiple choices summarised into the trigger once there are more than two.

Skills Multiple choices summarised into the trigger once there are more than two. preview
Skills

Keyboard interactions

KeysAction
Enter / SpaceOpens or closes the list; Enter toggles the active option while open.
ArrowDown / ArrowUpOpens the list and moves through enabled options, wrapping.
EscapeCloses the list.

Accessibility

  • Trigger has aria-haspopup="listbox", aria-expanded, and aria-labelledby combining the label and a hidden list of selected labels.
  • The menu is role="listbox" with aria-multiselectable; options carry aria-selected and aria-disabled.
  • Clear button is labelled "Clear selections"; chips are aria-hidden in favor of the spoken summary.

Motion

  • Chips open their slot width on a smooth spring and grow in from 0.9 with a blur; the +N count rolls up or down.
  • Checks draw in the menu; the menu springs in from slightly above.
  • Reduced motion turns every change into a short crossfade with no scale or width travel.

Responsive behavior

  • Chips cap at 9rem and ellipsize, and maxVisible limits how many show before +N, so the trigger holds one line.
  • The menu spans the field width and has no max height or scroll, so keep option lists short on small screens.

Performance

  • Options are not virtualized and the menu does not scroll; for long lists use combobox instead.
  • Chips animate slot width and scale per change; fine for a few selections.

Notes for AI

  • Use for picking several values from a fixed list in a compact field. Use chip-group when all options should stay visible and tag-input for free-form values.
  • Controlled with value and onValueChange or uncontrolled with defaultValue. There is no name prop, so serialize the array yourself for forms.