Multi Select
A typeahead multi-select combobox that allows users to select multiple values with filtering. Selected values are displayed as removable tags. Built on top of ariakit Combobox with full keyboard support and WAI-ARIA Combobox Pattern compliance.
When to use
Use Multi Select when the user can choose multiple values from a list, with selected values rendered as removable tags/chips. For single selection, use Combobox (with search) or Select (without).
Pair with Field.* for label/description/error wiring in forms: wrap MultiSelect.Root in Field.Control. Field.Control flows the generated id, aria-describedby, aria-errormessage, and aria-invalid through to the focusable MultiSelect.Input via FieldControlContext. The field name flows through too, but lands on one hidden input per selected value rather than on the combobox input: the input's value is the typeahead filter text, so a native form submit sends the selected values instead of what the user typed.
Pick one or more.
Or render the control on its own:
Layering and z-index
MultiSelect.Content renders at Tailwind z-50 and portals into the closest data-mantle-modal-content element — an enclosing overlay's content — else the nearest overlay's positioner, else document.body. Inside an overlay it paints above the content that contains it. Outside one, an open overlay (z-60) covers it.
MultiSelect does not render a backdrop by default. Modal layers such as Dialog, Sheet, and AlertDialog do render backdrops, so stacking those layers compounds the dim and blur effect on the layers underneath.
Examples
Controlled
Use selectedValue and setSelectedValue to control the selected values.
In a Sheet
Use @tanstack/react-form to manage the selected values when a side panel needs validation and submit handling. MultiSelect.Content will automatically scope its portal to the surrounding sheet so the default modal behavior works without extra configuration.
Locked values
Use lockedValues on MultiSelect.TagValues to pin specific tags that cannot be removed. Locked tags display a lock icon instead of an ✕ and shake when the user tries to remove them. Locked values also cannot be deselected by clicking them in the popover.
Creatable
Render a dynamic "Create …" item when the search text doesn't match any existing option. When selected, add the new value to both your options list and the selection. No component changes needed: consumers own the items rendered in Content.
With groups
Use MultiSelect.Group, MultiSelect.GroupLabel, and MultiSelect.Separator to organize items.
With TanStack Form
Use @tanstack/react-form with zod to wire selected values into form state. Pass field.state.value to selectedValue and field.handleChange to setSelectedValue.
Validation states
Use the validation prop on MultiSelect.Trigger to show validation states.
Region pinning
A real-world example with three groups (regional aliases, PoPs, dedicated IPs), disabled items, secondary metadata per item, a stacked item description layout, a trailing link in the trigger, and a sticky footer CTA inside the content.
Composition
Compose the parts of a MultiSelect together to build your own:
Polymorphism
MultiSelect.Item, MultiSelect.Content, MultiSelect.Group, MultiSelect.GroupLabel, and MultiSelect.Separator accept asChild, rendering your single child in place of the part's default element while keeping its selection and keyboard behavior.
MultiSelect.GroupLabel asChild is the useful one: reach for it when a group heading must be a specific element for the page's outline. MultiSelect.Content asChild lets you own the popover panel element.
The trigger-side parts (MultiSelect.Root, Trigger, Input, Tag, TagValues) own input and tag behavior that depends on the elements they render, so they do not accept asChild.
MultiSelect.Item asChild clones your single child as the option element and moves the label div and the check indicator inside it, so the translation guarantee holds on both paths. To give an option a custom layout inside the default element, pass it as children (a MediaObject with an avatar, say), which is unrestricted.
API Reference
The MultiSelect components are built on top of ariakit Combobox.
MultiSelect.Root
Root component for a multi-select combobox. Owns the selected values and the typeahead query.
MultiSelect.Content renders at Tailwind z-50 and portals into the closest data-mantle-modal-content element — an enclosing overlay's content — else the nearest overlay's positioner, else document.body. Inside an overlay it paints above the content that contains it. Outside one, an open overlay (z-60) covers it.
All props from ariakit ComboboxProvider (typed to string[] for multi-select), plus:
MultiSelect.Trigger
The trigger container for the multi-select. Wraps the input and selected value tags in a styled container that looks like a form input. It is role="presentation": the MultiSelect.Input combobox and the MultiSelect.TagValues list carry the semantics, and the input's label (Field.Label or aria-label) names the whole control. Give the input a label: the placeholder disappears after the first selection, so it cannot be the only name.
MultiSelect.TagValues
Renders the selected values as removable tags. Place this inside MultiSelect.Trigger, followed by MultiSelect.Input.
The tags render inside a role="list" named "Selected values" (override it with aria-label), and each default tag is a role="listitem". The list uses display: contents, so the tags keep the trigger's flex layout. A custom tag from the children render function must be a role="listitem" too.
TagRenderProps:
MultiSelect.Input
The combobox input for filtering items. Place this inside MultiSelect.Trigger, after MultiSelect.TagValues.
All props from ariakit Combobox. Key props:
MultiSelect.Tag
The default tag component rendered inside MultiSelect.TagValues for each selected value. Displays the value label with a remove button. Renders a role="listitem" inside the role="list" that MultiSelect.TagValues renders, and marks itself data-active while it has focus.
Arrow-key navigation and Backspace/Delete removal come from the MultiSelect.TagValues render props; spread them onto the tag to get them. On its own, Tag only blocks Backspace and Delete on a locked tag.
Use this when building a custom TagValues-like component and you want the default tag chrome with consistent styling.
Also accepts all standard span element props (except children). The onKeyDown prop is supported and is called after the internal locked-key handling.
MultiSelect.Content
Renders a popover that contains multi-select content.
MultiSelect.Content renders at Tailwind z-50 and portals into the closest data-mantle-modal-content element — an enclosing overlay's content — else the nearest overlay's positioner, else document.body. Inside an overlay it paints above the content that contains it. Outside one, an open overlay (z-60) covers it.
All props from ariakit ComboboxPopover, plus:
MultiSelect.Item
Renders a selectable item with a checkbox indicator inside a MultiSelect.Content.
children render inside a <div data-slot="multi-select-item-label">. The selected-state check is a permanent element sibling, so without the wrapper a bare text label is never a lone child: a browser translation engine reparents that text node, and the removal React runs when the label changes shape or goes away throws. The wrapper is a <div> because a custom option layout is often a MediaObject, whose root is a <div> that a <span> may not contain. It is display: contents, so a child of your own stays a flex item of the option and any flex-1 on it still resolves. Browser translation covers the mechanism and the rules your own components need.
All props from ariakit ComboboxItem, except render, plus:
MultiSelect.Group
Renders a group for MultiSelect.Item elements. Optionally, render a MultiSelect.GroupLabel as a child to label the group.
All props from ariakit ComboboxGroup, plus:
MultiSelect.GroupLabel
Renders a label in a multi-select group. Should be wrapped with MultiSelect.Group so the aria-labelledby is correctly set on the group element.
All props from ariakit ComboboxGroupLabel, plus:
MultiSelect.GroupDescription
Renders a descriptive paragraph inside a MultiSelect.Group, placed after MultiSelect.GroupLabel. Adds context about the group's items.
Standard p element props.
MultiSelect.Separator
Renders a separator between MultiSelect.Items or MultiSelect.Groups.
All props from Separator.
MultiSelect.Empty
Renders a message when no items match the current filter.
Standard div element props.
MultiSelect.ContentFooter
Renders a sticky footer inside MultiSelect.Content that stays pinned to the bottom edge of the content area. Place it as the last child of MultiSelect.Content.
Standard div element props.