OWolf

AboutBlogProjects
©2025 OWolf.com

Privacy

Contact

Web Development,Productivity

Create a Multi-Select Component Using ShadCN in Next.js

March 7, 2025

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

sh
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.


🔨 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 = />
        
      
      
        
          
          
            {filteredOptions.length === 0 ? (
              No options found.
            ) : (
              filteredOptions.map((option) => {
                const isSelected = selectedValues.includes(option.value);
                return (
                   toggleSelection(option.value)}
                  >
                    
                      
                      {option.label}
                    
                  
                );
              })
            )}
          
        
      
    
  );
};

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.

tsx
import 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!


▊
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>
<CommandEmpty>
</CommandEmpty>
<CommandItem key={option.value} onSelect={() =>
<div className="flex items-center">
<Check className={`mr-2 h-4 w-4 ${ isSelected ? "opacity-100" : "opacity-0" }`} />
</div>
</CommandItem>
</CommandList>
</Command>
</PopoverContent>
</Popover>