Select Dropdown + Searchbar + Clearable (React & Shadcn)

Shadcn provides a fantastic set of beautiful UI components right out of the box. One of the most commonly used components is a selector. However, the component from shadcn (which is based on Radix UI) lacks certain features, such as search functionality and the ability to clear selected options. In this guide, I'll be implementing a custom select dropdown component that supports searching and clearing options. Select Dropdown Let's start with a list of options: const options = [ { value: "apple": label: "Apple" }, { value: "banana": label: "Banana" }, { value: "avocado": label: "Avocado" }, // ... ]; First, I will create a basic dropdown using and that: Displays a list of options Shows a checkmark for the selected option Includes a close button import * as React from "react"; import { CheckIcon } from "lucide-react"; import { cn } from "@/lib/utils"; import { Command, CommandGroup, CommandItem, CommandList, CommandSeparator, } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; export type SelectOption = { value: string; label: string; }; export const InputSelect: React.FC void; className?: string; style?: React.CSSProperties; children: React.ReactNode; }> = ({ options, value = "", onValueChange, className, children, }) => { const [selectedValue, setSelectedValue] = React.useState(value); const [isPopoverOpen, setIsPopoverOpen] = React.useState(false); const onOptionSelect = (option: string) => { setSelectedValue(option); onValueChange?.(option); setIsPopoverOpen(false); }; return ( {children} {options.map((option) => { const isSelected = selectedValue === option.value; return ( onOptionSelect(option.value)} className="cursor-pointer" > {option.label} ); })} setIsPopoverOpen(false)} className="justify-center flex-1 max-w-full cursor-pointer" > Close ); }; InputSelect.displayName = "InputSelect"; Add Search Functionality To enhance usability, I'll integrate for built-in search capabilities and to display a message when no results are found. export const InputSelect = () => { // ... return ( No results found. // ... // ... ); }; Adding a Clear Option I also want to provide a button to clear the selected value when one is chosen. import { Separator } from "@/components/ui/separator"; export const InputSelect: React.FC = ({ ... }) => { // ... const onClearAllOptions = () => { setSelectedValue(""); onValueChange?.(""); setIsPopoverOpen(false); }; return ( // ... {selectedValue && ( Clear )} setIsPopoverOpen(false)} className="justify-center flex-1 max-w-full cursor-pointer" > Close ); }; So far so good, now I have a dropdown looking like so: Add Dropdown Trigger Now, for the last step, I can add a trigger to toggle open/close on the dropdown. I could just use a for that. import { Separator } from "@/components/ui/separator"; export const InputSelect: React.FC = ({ ... }) => { // ... const onClearAllOptions = () => { setSelectedValue(""); onValueChange?.(""); setIsPopoverOpen(false); }; return ( setIsPopoverOpen((prev) => !prev)} variant="outline" type="button" className="flex h-11 w-full items-center justify-between p-1 [&_svg]:pointer-events-auto" > {selectedValue ? ( {options.find((v) => v.value === selectedValue)?.label} {selectedValue && ( { e.stopPropagation(); onClearAllOptions(); }} />

Feb 17, 2025 - 00:27
 0
Select Dropdown + Searchbar + Clearable (React & Shadcn)

Shadcn provides a fantastic set of beautiful UI components right out of the box. One of the most commonly used components is a selector. However, the component from shadcn (which is based on Radix UI) lacks certain features, such as search functionality and the ability to clear selected options.

In this guide, I'll be implementing a custom select dropdown component that supports searching and clearing options.

Select Dropdown

Let's start with a list of options:

const options = [
  { value: "apple": label: "Apple" },
  { value: "banana": label: "Banana" },
  { value: "avocado": label: "Avocado" },
  // ...
];

First, I will create a basic dropdown using and that:

  • Displays a list of options
  • Shows a checkmark for the selected option
  • Includes a close button
import * as React from "react";
import { CheckIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import {
  Command,
  CommandGroup,
  CommandItem,
  CommandList,
  CommandSeparator,
} from "@/components/ui/command";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";

export type SelectOption = {
  value: string;
  label: string;
};

export const InputSelect: React.FC<{
  options: SelectOption[];
  value?: string;
  onValueChange?: (v: string) => void;
  className?: string;
  style?: React.CSSProperties;
  children: React.ReactNode;
}> = ({
  options,
  value = "",
  onValueChange,
  className,
  children,
}) => {
  const [selectedValue, setSelectedValue] = React.useState<string>(value);
  const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);

  const onOptionSelect = (option: string) => {
    setSelectedValue(option);
    onValueChange?.(option);
    setIsPopoverOpen(false);
  };

  return (
    <Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
      <PopoverTrigger asChild>
        {children}
      </PopoverTrigger>
      <PopoverContent className={cn("w-auto p-0", className)} align="start">
        <Command>
          <CommandList className="max-h-[unset] overflow-y-hidden">
            <CommandGroup className="max-h-[20rem] min-h-[10rem] overflow-y-auto">
              {options.map((option) => {
                const isSelected = selectedValue === option.value;
                return (
                  <CommandItem
                    key={option.value}
                    onSelect={() => onOptionSelect(option.value)}
                    className="cursor-pointer"
                  >
                    <div
                      className={cn(
                        "mr-1 flex h-4 w-4 items-center justify-center",
                        isSelected ? "text-primary" : "invisible"
                      )}
                    >
                      <CheckIcon className="w-4 h-4" />
                    </div>
                    <span>{option.label}</span>
                  </CommandItem>
                );
              })}
            </CommandGroup>
            <CommandSeparator />
            <CommandGroup>
              <div className="flex items-center justify-between">
                <CommandItem
                  onSelect={() => setIsPopoverOpen(false)}
                  className="justify-center flex-1 max-w-full cursor-pointer"
                >
                  Close
                </CommandItem>
              </div>
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
};
InputSelect.displayName = "InputSelect";

Add Search Functionality

To enhance usability, I'll integrate for built-in search capabilities and to display a message when no results are found.

export const InputSelect = () => {
  // ...
  return (
    <Popover {...}>
      <PopoverTrigger {...} />
      <PopoverContent {...}>
        <Command>
          <CommandInput placeholder="Search..." />
          <CommandList {...}>
            <CommandEmpty>No results found.</CommandEmpty>
            <CommandGroup {...}>
              // ...
            </CommandGroup>
            <CommandSeparator />
            <CommandGroup>
              // ...
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
};

Adding a Clear Option

I also want to provide a button to clear the selected value when one is chosen.

import { Separator } from "@/components/ui/separator";

export const InputSelect: React.FC<{ ... }> = ({ ... }) => {
  // ...

  const onClearAllOptions = () => {
    setSelectedValue("");
    onValueChange?.("");
    setIsPopoverOpen(false);
  };

  return (
    <Popover {...}>
      <PopoverTrigger {...} />
      <PopoverContent {...}>
        <Command>
          <CommandInput {...} />
          <CommandList {...}>
            <CommandEmpty {...} />
            <CommandGroup {...}>
              // ...
            </CommandGroup>
            <CommandSeparator />
            <CommandGroup>
              <div className="flex items-center justify-between">
                {selectedValue && (
                  <>
                    <CommandItem
                      onSelect={onClearAllOptions}
                      className="justify-center flex-1 cursor-pointer"
                    >
                      Clear
                    </CommandItem>
                    <Separator
                      orientation="vertical"
                      className="flex h-full mx-2 min-h-6"
                    />
                  </>
                )}
                <CommandItem
                  onSelect={() => setIsPopoverOpen(false)}
                  className="justify-center flex-1 max-w-full cursor-pointer"
                >
                  Close
                </CommandItem>
              </div>
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
};

So far so good, now I have a dropdown looking like so:

input-select-1

Add Dropdown Trigger

Now, for the last step, I can add a trigger to toggle open/close on the dropdown. I could just use a