React

Select con ricerca in shadcn: il pattern giusto

Per una select con ricerca usa Popover e Command, non il Select di Radix: il Select non ha un campo di testo e aggiungerne uno rompe la tastiera.

In sintesi

  • Il `Select` di shadcn non ha un campo di ricerca: il type-ahead nativo salta alle voci, non filtra.
  • Per filtrare usa il pattern combobox: `Popover` + `Command`, con `CommandInput` e `CommandList`.
  • Con più di qualche centinaio di voci il filtro va spostato sul server e `shouldFilter` messo a `false`.

Risposta breve: per una select con ricerca usa il pattern combobox, cioè Popover più Command. Il Select di Radix non ha un campo di testo, e aggiungerlo a mano rompe la tastiera e i lettori di schermo.

Perché il Select non basta

Il Select di Radix ha già il type-ahead: scrivi le lettere e la selezione salta alla voce che comincia così. Non ha però un campo visibile, quindi l'utente non vede cosa ha digitato e non può correggere una lettera. Fino a dieci voci il type-ahead è sufficiente, oltre diventa una scommessa.

npx shadcn@latest add command popover button

Il componente completo

"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}
                  
                ))}
              
            
          
        
      
    
  );
}

Quale componente per quale lista

VociComponenteMotivo
Fino a 10SelectIl type-ahead basta e il pattern è una riga
10-200Popover + CommandServe vedere il testo digitato e filtrare
Oltre 200, dati remotiCommand con shouldFilter={false}Il filtro va sul server, il client non scarica tutto

Il filtro remoto

Quando le voci arrivano da un endpoint, cmdk non deve filtrare: filtrerebbe solo la pagina già scaricata. Metti shouldFilter={false}, tieni la query in stato e passa i risultati che tornano dal server.


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

Fonte: la pagina Combobox di shadcn/ui (licenza MIT) per il pattern e il repository cmdk (licenza MIT) per il filtro; Command non è un componente ARIA combobox finito, quindi il ruolo va messo a mano sul trigger, come nel codice sopra.

Limite dichiarato

Il filtro di cmdk gira nel client: con 5.000 voci il browser scarica 5.000 righe prima di mostrare un risultato, e la ricerca fuzzy trova corrispondenze che l'utente non sa spiegare. Secondo limite: il campo perde il focus quando il popover si chiude e va riportato sul trigger, altrimenti la tastiera riparte dall'inizio della pagina.

Dove continuare

Nel catalogo trovi la libreria shadcn, un blocco select con anteprima e i blocchi della categoria form. Le basi sono in che cos'è shadcn/ui e la scelta tra librerie di form è in i migliori blocchi form del 2026; il confronto con un'altra libreria è shadcn contro MUI. Per l'altro lato del campo, la data, leggi calendar e date picker.

Domande frequenti

Posso aggiungere un campo di ricerca al Select?

No, non senza rifare il componente. Il Select di Radix gestisce la selezione con `aria-activedescendant` e non prevede un input: il pattern corretto è `Popover` + `Command`.

Quale valore passo a CommandItem?

Il `value` è ciò su cui cmdk filtra, quindi deve contenere il testo cercabile. Se l'etichetta è composta, passa le parole in più con `keywords`.

Come mai la ricerca non trova niente con i dati del server?

Perché `Command` sta filtrando una lista già filtrata. Con dati remoti metti `shouldFilter={false}` e lascia la query al server.

Il valore selezionato non arriva al form: cosa manca?

Un input nascosto con `name` e lo stesso valore dello stato. `Command` non è un controllo di form e non invia nulla da solo.

Meglio Command dentro un Popover o un Dialog?

Popover per un campo in linea, Dialog su mobile quando la lista è alta e serve tutto lo schermo. Il contenuto interno è lo stesso.