React

Command menu con shadcn

Il Command di shadcn è una lista filtrabile costruita su cmdk: mettila in un Dialog, aprila con ⌘K e assegna a ogni voce un value univoco.

In sintesi

  • Il Command shadcn è un wrapper su cmdk: filtro, selezione e navigazione da tastiera arrivano dalla libreria.
  • `value` è la stringa su cui cmdk cerca: due voci con lo stesso testo vanno separate con value espliciti.
  • Con `shouldFilter={false}` il filtro passa al server e la lista resta corretta anche con dati asincroni.

Risposta breve: npx shadcn@latest add command installa un wrapper su cmdk; metti Command dentro CommandDialog, apri con ⌘K e assegna a ogni CommandItem un value univoco. Filtro e navigazione da tastiera li gestisce la libreria.

Le parti del menu

Il componente non è un menu a tendina: è una lista di comandi con un campo di ricerca. Ogni parte ha un ruolo preciso, e i ruoli ARIA li applica cmdk, non il tuo CSS.

ParteRuolo
CommandDialogInvolucro modale: monta il Command dentro un Dialog e aggiunge titolo e descrizione per lo screen reader.
CommandInputCampo di ricerca con ruolo combobox; riceve il focus quando il menu si apre.
CommandListContenitore con ruolo listbox, scorre se le voci superano l'altezza.
CommandItemUna voce selezionabile; il testo o il value esplicito entrano nel filtro.
CommandEmptyMessaggio mostrato quando nessuna voce corrisponde.
CommandGroup / CommandSeparatorRaggruppano le voci e separano blocchi di comandi.

Aprire con ⌘K, senza sorprese

La scorciatoia si registra su document in un effetto. preventDefault() serve: senza, su Windows Ctrl+K e su macOS ⌘K attivano la ricerca del browser. La stessa gesture deve funzionare anche dentro un campo di testo, quindi non filtrare per event.target.

"use client";

import { useEffect, useState } from "react";
import {
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
  CommandShortcut,
} from "@/components/ui/command";

export function CommandMenu() {
  const [open, setOpen] = useState(false);

  useEffect(() => {
    function onKeyDown(event: KeyboardEvent) {
      if (event.key === "k" && (event.metaKey || event.ctrlKey)) {
        event.preventDefault();
        setOpen((value) => !value);
      }
    }
    document.addEventListener("keydown", onKeyDown);
    return () => document.removeEventListener("keydown", onKeyDown);
  }, []);

  return (
    
      
      
        Nessun risultato.
        
           setOpen(false)}>
            Dashboard
            G D
          
           setOpen(false)}>
            Impostazioni
          
        
        
        
           setOpen(false)}>
            Esporta CSV
          
        
      
    
  );
}

Il filtro: dove si sbaglia

cmdk confronta ciò che scrivi con il value di ogni voce. Se value manca, usa il testo del nodo: due voci chiamate «Apri» producono due risultati identici e la selezione diventa ambigua. La correzione costa una riga: value descrittivo, testo visibile libero.

Quando le voci arrivano da una ricerca remota, il filtro locale combatte con i risultati del server: scrivi «re» e cmdk nasconde le voci che il server ha appena restituito. In quel caso passa shouldFilter={false} e gestisci tu lista, stato di caricamento e nessun risultato.


  
  
    {loading && Caricamento…}
    {!loading && results.length === 0 && (
      Nessun utente trovato.
    )}
    
      {results.map((user) => (
         pick(user)}>
          {user.name}
        
      ))}
    
  

Accessibilità

Il CommandDialog di shadcn accetta title e description e li rende in un header nascosto, perché un Dialog Radix senza titolo rompe la navigazione assistita. Se costruisci il dialog a mano, aggiungi DialogTitle: il caso è descritto in DialogContent richiede DialogTitle. La lista invece è già a posto: frecce, Home, End e Invio funzionano senza codice.

Limite dichiarato

cmdk filtra in memoria, nel browser. Con qualche centinaio di voci è istantaneo; con decine di migliaia il costo si sposta sul thread principale e il filtro remoto non è più un'opzione. Il catalogo, misurato il 1 novembre 2026, contiene 19 blocchi command: tutti presuppongono una lista breve e locale.

Dove continuare

Nel catalogo trovi un blocco command menu, il record del componente command di Origin UI e la libreria shadcn. Per il montaggio leggi che cosa è shadcn/ui e come aggiungere un blocco shadcn; il confronto tra cataloghi è in 21st, shadcnspace e le altre.

Domande frequenti

Serve una libreria esterna per il Command?

Sì, cmdk: il componente shadcn è un wrapper con lo stile del design system. Installa con npx shadcn@latest add command e la dipendenza arriva con il file.

Perché alcune voci non compaiono nella ricerca?

Perché il filtro confronta il value, non solo il testo visibile. Se value è un id o una stringa diversa dalla label, cerca quella; oppure imposta value sulla label e tieni l'id in un campo separato.

Come apro il menu con ⌘K?

Aggiungi un listener keydown su document, controlla event.metaKey o event.ctrlKey con key === "k" e chiama preventDefault. Il listener va registrato dentro un effetto e rimosso al cleanup.

Posso filtrare i risultati sul server?

Sì. Passa shouldFilter={false}, porta query in uno stato controllato e gestisci tu lista, caricamento e stato vuoto. Il filtro locale altrimenti nasconde i risultati appena arrivati.

Il menu funziona con la tastiera?

Sì: cmdk gestisce frecce, Home, End, Invio ed Escape. Devi solo dare un titolo al Dialog, perché senza DialogTitle la navigazione assistita perde il contesto.