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

> Page: https://ui.eneawork.it/blog/shadcn-select-ricerca
> Markdown: https://ui.eneawork.it/blog/shadcn-select-ricerca.md

*2026-11-01*

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.

cmdk, il pacchetto dietro Command, è alla versione 1.1.1 nel registro npm, verificata il 1 novembre 2026. Nell'indice dei blocchi pubblicato 16 blocchi hanno «select» nel nome e 19 hanno «command», contati il 1 novembre 2026. Il catalogo EneUI misura 111.985 componenti, 1.556 blocchi e 12.808 icone da 1.472 librerie, letti da /api/stats il 1 novembre 2026.

## 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

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

## FAQ

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