2025-03-07 Web Development, Productivity
Create a Multi-Select Component Using ShadCN in Next.js
By O. Wolfson
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.
Multi-Select Component Example:
Step by step guide to build the multi-select component:
📌 Prerequisites
Before we begin, ensure you have ShadCN installed in your Next.js project.
Install ShadCN Components
shnpx 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.
🔨 Building the Multi-Select Component
1️⃣ Create the MultiSelect.tsx
Component
Inside your components/ui
directory, create a new file MultiSelect.tsx
and add the following code:
tsx"use client";
import { useState } from "react";
import {
Popover,
PopoverTrigger,
PopoverContent,
} from "@/components/ui/popover";
import {
Command,
CommandInput,
CommandList,
CommandItem,
CommandEmpty,
} from "@/components/ui/command";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Check, ChevronsUpDown, X } from "lucide-react";
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;
2️⃣ How to Use the Multi-Select Component
Now that we have created the multi-select component, let's use it in a form.
tsximport React, { useState } from "react";
import MultiSelect from "@/components/ui/MultiSelect";
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>
);
}
Final Thoughts
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.
Features Recap
✅ 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!