Multi-select dropdowns are crucial for user-friendly forms, allowing users to pick multiple options efficiently. ShadCN provides excellent UI components, but lacks a built-in multi-select component. In this guide, we'll build a custom multi-select dropdown using ShadCN's Popover, Command, and Badge components.
Component "MultiSelect" is not available. Please define it in the MDX components.
Before we begin, ensure you have ShadCN installed in your Next.js project.
npx shadcn-ui@latest init # Initialize ShadCN if not installed
npx shadcn-ui@latest add popover command button badge
This installs the required components: Popover, Command, Button, and Badge.
MultiSelect.tsx ComponentInside your components/ui directory, create a new file MultiSelect.tsx and add the following code:
"use client";
interface Option {
value: string;
label: string;
}
interface MultiSelectProps {
options: Option[];
selectedValues: string[];
setSelectedValues: (values: string[]) => void;
placeholder?: string;
}
const MultiSelect: React.FC<MultiSelectProps> = ({
options,
selectedValues,
setSelectedValues,
placeholder,
}) => {
const [open, setOpen] = useState(false);
const [inputValue, setInputValue] = useState("");
const filteredOptions = options.filter((option) =>
option.label.toLowerCase().includes(inputValue.toLowerCase())
);
const toggleSelection = (value: string) => {
if (selectedValues.includes(value)) {
setSelectedValues(selectedValues.filter((item) => item !== value));
} else {
setSelectedValues([...selectedValues, value]);
}
};
const removeSelected = (value: string) => {
setSelectedValues(selectedValues.filter((item) => item !== value));
};
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
className="flex justify-between px-2 pb-2 items-center h-full min-w-[200px]"
variant="outline"
>
<div className="flex gap-1 flex-wrap">
{selectedValues.length > 0 ? (
selectedValues.map((val, index) => (
<Badge
key={val}
className="flex items-center gap-1 px-2 py-1 bg-gray-200 text-black dark:bg-gray-700 dark:text-white rounded-md"
>
{options.find((opt) => opt.value === val)?.label}
<div
onClick={(e) => {
e.stopPropagation();
removeSelected(val);
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.stopPropagation();
removeSelected(val);
}
}}
className="ml-1 text-red-500 hover:text-red-700 cursor-pointer"
>
<X className="h-3 w-3" />
</div>
</Badge>
))
) : (
<span className="text-gray-500">
{placeholder || "Select options..."}
</span>
)}
</div>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] p-0" align="start">
<Command>
<CommandInput
placeholder="Search..."
value={inputValue}
onValueChange={setInputValue}
/>
<CommandList>
{filteredOptions.length === 0 ? (
<CommandEmpty>No options found.</CommandEmpty>
) : (
filteredOptions.map((option) => {
const isSelected = selectedValues.includes(option.value);
return (
<CommandItem
key={option.value}
onSelect={() => toggleSelection(option.value)}
>
<div className="flex items-center">
<Check
className={`mr-2 h-4 w-4 ${
isSelected ? "opacity-100" : "opacity-0"
}`}
/>
{option.label}
</div>
</CommandItem>
);
})
)}
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
};
export default MultiSelect;
Now that we have created the multi-select component, let's use it in a form.
const options = [
{ value: "react", label: "React" },
{ value: "nextjs", label: "Next.js" },
{ value: "vue", label: "Vue.js" },
{ value: "angular", label: "Angular" },
];
export default function MultiSelectExample() {
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
return (
<div className="p-4">
<MultiSelect
options={options}
selectedValues={selectedCategories}
setSelectedValues={setSelectedCategories}
placeholder="Select frameworks..."
/>
<p className="mt-4">Selected: {selectedCategories.join(", ")}</p>
</div>
);
}
By using ShadCN components, we successfully built a fully functional, accessible multi-select dropdown with search functionality. This approach allows for highly customizable and reusable components in your Next.js app.
✅ Searchable dropdown with real-time filtering
✅ Allows multiple selections
✅ Displays selected items as badges with remove buttons
✅ Styled using ShadCN components for seamless UI integration
Now you have a powerful multi-select dropdown component ready to use!