Skip to content

Documentation

neelam-ui

Search documentation

Getting Started
Forms
Overlays
Navigation
Data Display
Layout
AI & Chat
Blocks
GitHub repository

Command

A searchable list of actions — the embeddable form of a command palette, which CommandDialog wraps into the ⌘K overlay most people picture.

Suggestions
Settings

Usage#

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from "neelam-ui";
 
<Command>
  <CommandInput placeholder="Type a command or search…" />
  <CommandList>
    <CommandGroup heading="Suggestions">
      <CommandItem value="calendar" onSelect={run}>Calendar</CommandItem>
    </CommandGroup>
    <CommandEmpty>No results found.</CommandEmpty>
  </CommandList>
</Command>

Command on its own works planted directly in a page — a docs site's search box, for instance. It is not tied to a modal.

As a ⌘K dialog#

CommandDialog wraps the same parts in a Dialog:

const [open, setOpen] = useState(false);
 
<CommandDialog open={open} onOpenChange={setOpen}>
  <CommandInput placeholder="Type a command…" />
  <CommandList>…</CommandList>
</CommandDialog>

Its title is the dialog's accessible name and is visually hidden by default ("Command palette"), since a palette jumps straight to its input rather than showing a heading. Binding the keyboard shortcut is yours to do — the component does not install a global listener on your behalf.

Item labels must be plain strings#

CommandItem's children is typed as string, not arbitrary nodes. Filtering needs an item's text before deciding whether to render it at all, so the label has to be knowable up front.

Mixing an icon into children crashes

This is not a style rule. Filtering short-circuits past .toLowerCase() while the query is still empty, so a non-string child does not fail on mount — it fails the moment someone actually types. Use the icon prop, which exists for exactly this.

<CommandItem value="profile" icon={<User className="h-4 w-4" aria-hidden="true" />}>
  Profile
</CommandItem>

value identifies the item, is what the query matches against, and is what onSelect receives.

Empty and filtered states#

A non-matching CommandItem renders null entirely, not merely hidden. CommandGroup hides itself — heading included — once every item inside it has been filtered out, rather than leaving a heading floating over nothing. CommandEmpty renders only when nothing at all matches; include it, or a fruitless search collapses into a blank box.

Keyboard#

Keyboard shortcuts
KeyBehaviour
A–ZFilters the list. Focus never leaves the input.
Moves the highlight through the visible results.
EnterRuns the highlighted item's onSelect.
EscapeCloses the palette, when used inside CommandDialog.

Accessibility#

  • CommandInput is a native <input> with aria-autocomplete="list" and aria-controls. It is deliberately not role="combobox": there is no persistent selected value here the way there is in Combobox, only a live filter.
  • The highlight is tracked with aria-activedescendant, so real focus stays in the input and the user can keep typing to refine the search.
  • CommandList is role="listbox" and defaults aria-label to "Results" — a listbox takes no name from its content, so without it axe reports no accessible name at all. Override it for something more specific.
  • When filtering leaves nothing visible, the listbox role is dropped altogether: a listbox whose only child is an empty-state message is an aria-required-children violation, and aria-activedescendant would have nothing to point at.
  • CommandGroup is labelled by its own heading via aria-labelledby.

API reference#

Command#

Props for Command
PropTypeDefault
defaultOpen

Initial open state when uncontrolled. Defaults to `false`.

boolean
onOpenChange

Called whenever the open state changes, whether from `DialogTrigger`, `DialogClose`, the built-in close button, Escape, or an outside click.

((open: boolean) => void)
open

Controls the open state. Omit to let the dialog manage its own state.

boolean
title

The accessible name for the dialog — visually hidden, since a command palette jumps straight to its search input rather than showing a visible heading the way `Dialog` normally does. Defaults to `"Command palette"`.

ReactNodeCommand palette

CommandDialog#

Props for CommandDialog
PropTypeDefault
childrenReactNode
defaultOpen

Initial open state when uncontrolled. Defaults to `false`.

boolean
onOpenChange

Called whenever the open state changes, whether from `DialogTrigger`, `DialogClose`, the built-in close button, Escape, or an outside click.

((open: boolean) => void)
open

Controls the open state. Omit to let the dialog manage its own state.

boolean
title

The accessible name for the dialog — visually hidden, since a command palette jumps straight to its search input rather than showing a visible heading the way `Dialog` normally does. Defaults to `"Command palette"`.

ReactNode"Command palette"

CommandGroup#

Props for CommandGroup
PropTypeDefault
headingrequiredstring

CommandItem#

Props for CommandItem
PropTypeDefault
childrenrequired

The visible label — a plain string, not arbitrary `children`, the same constraint `ComboboxItem` has and for the same reason: filtering needs an item's text before deciding whether to render it at all. Mixing an icon element in here (rather than using `icon` below) isn't just a style choice this rules out — it's a real crash, caught directly against this component's own first "as a command palette" story: filtering short-circuits past `.toLowerCase()` while the query is still empty, so it doesn't fail immediately, only the moment someone actually types something.

string
valuerequired

Identifies this item — matched against the query, and passed to `onSelect`.

string
icon

An icon shown before the label — the place for one, since `children` can't hold anything but the label text.

ReactNode
onSelect

Called when this item is activated (clicked, or Enter while it's highlighted). Named to match `value`'s own vocabulary, not the native `onSelect` (a text-selection event every `HTMLAttributes` element technically has) it shadows — `Omit`ted from the base props above so this one, with its own unrelated signature, can take its place.

((value: string) => void)