Multi Select
FormMulti-value selector with tag-based display.
Preview
Select technologies...
Usage
example.jsx
import { MultiSelect } from "@/components/ui/multi-select";
export default function Example() {
return <MultiSelect />;
}Source Code
Copy this file into components/ui/multi-select.jsx in your project.
multi-select.jsx
"use client";
import { forwardRef, useState, useRef, useEffect } from "react";
import { cn } from "@/lib/utils";
const MultiSelect = forwardRef(
({ className, options = [], value = [], onValueChange, placeholder = "Select...", ...props }, ref) => {
const [open, setOpen] = useState(false);
const wrapperRef = useRef(null);
useEffect(() => {
const handleClick = (e) => {
if (wrapperRef.current && !wrapperRef.current.contains(e.target)) setOpen(false);
};
document.addEventListener("mousedown", handleClick);
return () => document.removeEventListener("mousedown", handleClick);
}, []);
const toggle = (val) => {
const next = value.includes(val) ? value.filter((v) => v !== val) : [...value, val];
onValueChange?.(next);
};
const remove = (val) => onValueChange?.(value.filter((v) => v !== val));
const selectedLabels = options.filter((o) => value.includes(o.value));
return (
<div ref={wrapperRef} className={cn("relative", className)} {...props}>
<div
ref={ref}
onClick={() => setOpen((o) => !o)}
className="flex min-h-9 w-full flex-wrap items-center gap-1 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm cursor-pointer"
>
{selectedLabels.length === 0 && (
<span className="text-muted-foreground">{placeholder}</span>
)}
{selectedLabels.map((opt) => (
<span key={opt.value} className="inline-flex items-center gap-1 rounded-md bg-secondary px-2 py-0.5 text-xs font-medium">
{opt.label}
<button type="button" onClick={(e) => { e.stopPropagation(); remove(opt.value); }} className="hover:text-destructive cursor-pointer">×</button>
</span>
))}
</div>
{open && (
<div className="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border bg-popover p-1 shadow-md">
{options.map((opt) => (
<button
key={opt.value}
type="button"
onClick={() => toggle(opt.value)}
className={cn(
"flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm cursor-pointer hover:bg-accent",
value.includes(opt.value) && "bg-accent text-accent-foreground"
)}
>
<span className={cn("flex h-4 w-4 items-center justify-center rounded border border-primary", value.includes(opt.value) && "bg-primary text-primary-foreground")}>
{value.includes(opt.value) && "✓"}
</span>
{opt.label}
</button>
))}
</div>
)}
</div>
);
}
);
MultiSelect.displayName = "MultiSelect";
export { MultiSelect };
Quick Install
Make sure you have the cn() utility set up. It requires clsx and tailwind-merge.
npm install clsx tailwind-merge