Combobox
FormSearchable dropdown with autocomplete filtering.
Preview
Usage
example.jsx
import { Combobox } from "@/components/ui/combobox";
export default function Example() {
return <Combobox />;
}Source Code
Copy this file into components/ui/combobox.jsx in your project.
combobox.jsx
"use client";
import { forwardRef, useState, useRef, useEffect } from "react";
import { cn } from "@/lib/utils";
const Combobox = forwardRef(
({ className, options = [], value, onValueChange, placeholder = "Search...", ...props }, ref) => {
const [open, setOpen] = useState(false);
const [query, setQuery] = useState("");
const wrapperRef = useRef(null);
const filtered = options.filter((opt) =>
opt.label.toLowerCase().includes(query.toLowerCase())
);
const selected = options.find((opt) => opt.value === value);
useEffect(() => {
const handleClick = (e) => {
if (wrapperRef.current && !wrapperRef.current.contains(e.target)) setOpen(false);
};
document.addEventListener("mousedown", handleClick);
return () => document.removeEventListener("mousedown", handleClick);
}, []);
return (
<div ref={wrapperRef} className={cn("relative", className)} {...props}>
<input
ref={ref}
type="text"
role="combobox"
aria-expanded={open}
value={open ? query : selected?.label || ""}
placeholder={placeholder}
onFocus={() => { setOpen(true); setQuery(""); }}
onChange={(e) => setQuery(e.target.value)}
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
/>
{open && (
<div className="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border bg-popover p-1 shadow-md">
{filtered.length === 0 && (
<div className="px-2 py-1.5 text-sm text-muted-foreground">No results.</div>
)}
{filtered.map((opt) => (
<button
key={opt.value}
type="button"
onClick={() => { onValueChange?.(opt.value); setOpen(false); setQuery(""); }}
className={cn(
"flex w-full items-center rounded-sm px-2 py-1.5 text-sm cursor-pointer hover:bg-accent hover:text-accent-foreground",
value === opt.value && "bg-accent text-accent-foreground"
)}
>
{opt.label}
</button>
))}
</div>
)}
</div>
);
}
);
Combobox.displayName = "Combobox";
export { Combobox };
Quick Install
Make sure you have the cn() utility set up. It requires clsx and tailwind-merge.
npm install clsx tailwind-merge