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
| Voci | Componente | Motivo |
|---|---|---|
| Fino a 10 | Select | Il type-ahead basta e il pattern è una riga |
| 10-200 | Popover + Command | Serve vedere il testo digitato e filtrare |
| Oltre 200, dati remoti | Command 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.