React

A searchable select in shadcn: the right pattern

For a searchable select use Popover with Command, not the Radix Select: Select has no text input, and adding one breaks the keyboard and screen readers.

Key points

  • The shadcn `Select` has no search input: the native type-ahead jumps to entries, it does not filter.
  • To filter, use the combobox pattern: `Popover` plus `Command`, with `CommandInput` and `CommandList`.
  • Past a few hundred options the filter belongs on the server, with `shouldFilter` set to `false`.

Short answer: for a searchable select use the combobox pattern, which is Popover plus Command. The Radix Select has no text input, and bolting one on breaks the keyboard and screen readers.

Why Select is not enough

The Radix Select already has type-ahead: you type letters and the selection jumps to the entry that starts with them. It has no visible input though, so the user cannot see what was typed and cannot correct a letter. Up to ten options type-ahead is enough, past that it becomes a gamble.

npx shadcn@latest add command popover button

The whole component

"use client";

import { useState } from "react";
import { Check, ChevronsUpDown } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from "@/components/ui/command";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";

type Option = { value: string; label: string };

export function SearchSelect({
  name,
  options,
  placeholder = "Cerca...",
}: {
  name: string;
  options: Option[];
  placeholder?: string;
}) {
  const [open, setOpen] = useState(false);
  const [selected, setSelected] = useState("");

  const current = options.find((option) => option.value === selected);

  return (
    
      
      
        
          
            {current ? current.label : placeholder}
            
          
        
        
          
            
            
              Nessun risultato.
              
                {options.map((option) => (
                   {
                      setSelected(value === selected ? "" : value);
                      setOpen(false);
                    }}
                  >
                    
                    {option.label}
                  
                ))}
              
            
          
        
      
    
  );
}

Which component for which list

OptionsComponentReason
Up to 10SelectType-ahead is enough and the pattern is one line
10-200Popover plus CommandYou need to see the typed text and filter on it
Over 200, remote dataCommand with shouldFilter={false}The filter belongs on the server, the client does not download everything

Remote filtering

When options come from an endpoint, cmdk must not filter: it would only filter the page it already downloaded. Set shouldFilter={false}, keep the query in state and pass the results the server returns.


  
  
    {loading ? "Carico..." : "Nessun risultato."}
    {results.map((option) => (
       pick(option.value)}
      >
        {option.label}
      
    ))}
  

Source: the shadcn/ui Combobox page (MIT licence) for the pattern and the cmdk repository (MIT licence) for the filter; Command is not a finished ARIA combobox, so the role goes on the trigger by hand, as in the code above.

A named limitation

The cmdk filter runs in the client: with 5,000 options the browser downloads 5,000 rows before showing a result, and fuzzy search finds matches the user cannot explain. A second limit: the input loses focus when the popover closes and has to go back to the trigger, or the keyboard restarts from the top of the page.

Where to continue

In the catalogue you find the shadcn library, a select block with preview and the form category blocks. The basics are in what shadcn/ui is and the choice between form libraries is in the best form blocks of 2026; the comparison with another library is shadcn versus MUI. For the other half of the field, the date, read calendar and date picker.

Frequently asked questions

Can I add a search input to Select?

No, not without rewriting the component. The Radix Select manages selection through `aria-activedescendant` and has no input: the correct pattern is `Popover` plus `Command`.

Which value do I pass to CommandItem?

The `value` is what cmdk filters on, so it must hold the searchable text. When the label is composite, pass the extra words through `keywords`.

Why does search find nothing with server data?

Because `Command` is filtering a list that is already filtered. With remote data set `shouldFilter={false}` and leave the query to the server.

The selected value never reaches the form. What is missing?

A hidden input with `name` holding the same value as the state. `Command` is not a form control and submits nothing by itself.

Command inside a Popover or a Dialog?

Popover for an inline field, Dialog on mobile when the list is tall and needs the whole screen. The content inside is the same.