Skip to content

Multi-Select Combobox

A tag picker in the style of a command-bar label picker: chips that spring in and collapse out while neighbours glide, an overflow +N chip, grouped options with colour dots, fuzzy search with matched letters highlighted, a sliding active row, drawn-in checks, and a Create row.

DOM + CSS0 deps1 file · 30 KB TSXreduced-motion aware

Prominimalglass
Preview

Props

5
3
Create row

The source is part of Pro

$27/month or $179 once. See Pro

No runtime dependencies
"use client";

import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type KeyboardEvent } from "react";

export type ComboOption = { value: string; label?: string; group?: string; color?: string };

type Opt = { value: string; label: string; group: string; color: string };
type Hit = { opt: Opt; hits: number[]; score: number };
type Ghost = { id: number; label: string; color: string; x: number; y: number; w: number; h: number };
type Pos = { x: number; y: number; w: number; h: number };

const PALETTE = ["#7c8cff", "#34d3a0", "#ff8a5c", "#f2c14e", "#e879c6", "#5cc8ff", "#a78bfa", "#ff6b81", "#8bd450", "#4fd1c5"];
const ACCENT = "#8ea2ff";

const DEFAULT_OPTIONS: ComboOption[] = [
  { value: "React", group: "UI libraries" },
  { value: "Vue", group: "UI libraries" },
  { value: "Svelte", group: "UI libraries" },

  // ---------------------------------------------------------------
  // Full source available with a viberdy Pro subscription.
  // https://viberdy.dev/pricing
  // ---------------------------------------------------------------
}
Details

About this component

A tag picker built like a command-bar label picker. One input is the search box, the trigger and the chip remover. Selected values sit before the caret as chips that scale and fade in; when one leaves, a ghost fades out at its old spot while the remaining chips glide into place (a FLIP move measured from layout positions), so wrapped rows never jump. Past maxVisible chips the rest collapse into a +N chip that expands on click and bumps when the count changes. The popover lists options in groups, each with a colour dot. Typing runs a fuzzy match: a contiguous hit ranks above a word-start subsequence, which ranks above a loose one, and the matched letters are underlined. A single highlight bar slides between rows instead of each row flashing, check marks draw themselves in with a stroke animation, and when the text matches no existing label a Create row adds it as a new option. A header shows the count against maxSelected with Select all and Clear; at the cap, unselected rows dim and refuse. Props: maxSelected and placeholder (as before) plus options (strings or {value,label,group,color}), value/defaultValue/onChange for controlled use, defaultOpen, maxVisible, allowCreate, onCreate, label, disabled and className. The root is w-full max-w-xs by default; pass className such as !max-w-none to widen it.

Curator’s note

Rebuilt in October as a Linear-style label picker: no icon dependency, a real FLIP for chip removal, and a Create row. Backspace removes a chip only when the query is empty, so editing text never deletes a selection by accident.

Pairs well with

Pro
Pro