Multi Select
A tag-style multi-select with keyboard chips, filtering, and accessible removal — one component, tokened across themes.
Real use · pick several frameworks (string items)
Object items · label ≠ value
Sizes
Disabled
Using Multi Select
Silica ships one design system through three paths. They share a class vocabulary but are not interchangeable — pick the one that matches what you are building.
Plain HTML — CSS classes
The Tailwind v4 plugin. Works in any framework or none — Rails, Django, PHP, Go, a static file. This path ships no JavaScript, so Multi Select is styled but inert: its behavior comes from combobox. For that behavior with no framework, use the node-tree path below.
/* in your CSS */
@import "tailwindcss";
@plugin "@wizeworks/silicaui";Root class multi-select, with 19 modifiers:
.multi-select.multi-select-accent.multi-select-chip.multi-select-chip-remove.multi-select-chips.multi-select-clear.multi-select-error.multi-select-info.multi-select-input.multi-select-lg.multi-select-md.multi-select-neutral.multi-select-primary.multi-select-secondary.multi-select-sm.multi-select-success.multi-select-trigger.multi-select-warning.multi-select-xl.multi-select-xs
React
Typed components over those same classes, with behavior and accessibility from Base UI.
npm i @wizeworks/silicaui-reactimport { MultiSelect } from "@wizeworks/silicaui-react";Props
MultiSelectItemProps
| Prop | Type | Required | Description |
|---|---|---|---|
| value | unknown | yes | — |
| children | React.ReactNode | no | — |
MultiSelectProps
| Prop | Type | Required | Description |
|---|---|---|---|
| items | readonly unknown[] | yes | The full option set (strings or { value, label }); Base UI filters it. |
| value | unknown[] | no | Controlled selected values. |
| defaultValue | unknown[] | no | Uncontrolled initial selected values. |
| onValueChange | (value: unknown[], eventDetails?: unknown) => void | no | Fires with the newly-selected value array. |
| name | string | no | — |
| disabled | boolean | no | — |
| required | boolean | no | — |
| placeholder | string | no | — |
| emptyMessage | React.ReactNode | no | Shown in the popup when nothing matches the query. |
| color | MultiSelectColor | no | Accent for the field border, focus ring, and chips. |
| size | MultiSelectSize | no | Field height; matches same-size Inputs. |
| clearable | boolean | no | Show the clear-all (×) button (default true). |
| side | MultiSelectSide | no | — |
| align | MultiSelectAlign | no | — |
| sideOffset | number | no | — |
| className | string | no | Class for the field. |
| popupClassName | string | no | Class for the popup surface. |
| itemToStringLabel | (item: unknown) => string | no | For object items, map an item to its display string (default: .label). |
| renderItem | (item: unknown, index: number) => React.ReactNode | no | Override how each filtered dropdown item renders. |
| renderChipLabel | (item: unknown) => React.ReactNode | no | Override how each selected value's chip label renders (default: .label). |
| "aria-label" | string | no | — |
| "aria-labelledby" | string | no | — |
| id | string | no | — |
| anchor | BasePositionerProps["anchor"] | no | What to position against. Defaults to the component's own trigger. |
| positionMethod | BasePositionerProps["positionMethod"] | no | absolute (default) or fixed. Use fixed to escape a clipping ancestor. |
| alignOffset | BasePositionerProps["alignOffset"] | no | Offset along the alignment axis, in px. Pairs with align. |
| collisionBoundary | BasePositionerProps["collisionBoundary"] | no | The element the popup must stay inside. Defaults to the viewport — set it to a scroll container so the popup collides with the panel, not the window. |
| collisionPadding | BasePositionerProps["collisionPadding"] | no | Inset from the collision boundary, in px. |
| collisionAvoidance | BasePositionerProps["collisionAvoidance"] | no | Which collision strategy runs per axis (flip, shift, none). |
| sticky | BasePositionerProps["sticky"] | no | Keep the popup glued to the anchor while it scrolls out of view. |
| arrowPadding | BasePositionerProps["arrowPadding"] | no | Minimum distance from the popup's corner to the arrow, in px. |
| disableAnchorTracking | BasePositionerProps["disableAnchorTracking"] | no | Stop re-measuring when the anchor moves. Cheaper, but goes stale. |
Source of the demo above
The exact file that renders the examples above, so it can never drift from them. It imports a few of this site’s own layout helpers (../lib/…), so read it for the component calls rather than copying it whole.
Show source
import { useState } from "react";
import { MultiSelect } from "@wizeworks/silicaui-react";
import { Section, Row } from "../lib/Section";
const FRAMEWORKS = ["React", "Vue", "Svelte", "Solid", "Angular", "Qwik", "Preact"];
const COUNTRIES = [
{ value: "us", label: "United States" },
{ value: "ca", label: "Canada" },
{ value: "mx", label: "Mexico" },
{ value: "gb", label: "United Kingdom" },
{ value: "fr", label: "France" },
{ value: "de", label: "Germany" },
];
export function MultiSelectDemo() {
const [frameworks, setFrameworks] = useState<string[]>(["React", "Svelte"]);
const [countries, setCountries] = useState<{ value: string; label: string }[]>([]);
return (
<>
<Section title="Real use · pick several frameworks (string items)">
<MultiSelect
items={FRAMEWORKS}
value={frameworks}
onValueChange={(v) => setFrameworks(v as string[])}
placeholder="Add a framework…"
color="primary"
/>
</Section>
<Section title="Object items · label ≠ value">
<MultiSelect
items={COUNTRIES}
value={countries}
onValueChange={(v) => setCountries(v as { value: string; label: string }[])}
placeholder="Add countries…"
/>
</Section>
<Section title="Sizes">
<Row>
{(["sm", "md", "lg"] as const).map((size) => (
<MultiSelect
key={size}
items={["A", "B", "C"]}
defaultValue={["A"]}
size={size}
placeholder={size}
/>
))}
</Row>
</Section>
<Section title="Disabled">
<MultiSelect items={FRAMEWORKS} defaultValue={["Vue"]} disabled />
</Section>
</>
);
}Vanilla — node tree to HTML
The framework-neutral form of Multi Select. Build a node tree, project it with toHtml(), and let the zero-dependency runtime hydrate it. This is the path for generated or user-authored documents — a site builder, CMS output, a static export.
npm i @wizeworks/silicaui-html @wizeworks/silicaui-behaviorsimport { atom, toHtml } from "@wizeworks/silicaui-html";
const node = atom("MultiSelect", "multi-select", { /* props */ });
const markup = toHtml(node);Styling on this path is the class string in the second argument — the same classes as the Plain HTML section above. There is no color prop here; write "multi-select multi-select-primary" rather than passing a colour as a prop.
- Takes children
- no
- Hydrated by
combobox
Load @wizeworks/silicaui-behaviors on the page or this markup renders correctly and does nothing.
The full API for Multi Select, on every path, is available through the Silica MCP server.