Command
A searchable list of commands with groups, keyboard navigation and shortcuts. Use it inline, or as a ⌘K command palette with Command.Dialog.
Last command: nothing yet
import { Command } from "@meridui/react";
export function Example() {
return (
<Command.Root onSelect={(value) => run(value)}>
<Command.Input />
<Command.List>
<Command.Empty />
<Command.Group heading="Pages">
<Command.Item keywords={["dashboard"]}>Home</Command.Item>
<Command.Item>Projects</Command.Item>
<Command.Item>Settings</Command.Item>
</Command.Group>
<Command.Separator />
<Command.Group heading="Actions">
<Command.Item value="New project" shortcut={["mod", "shift", "p"]}>New project</Command.Item>
<Command.Item value="Invite member" shortcut={["mod", "shift", "i"]}>Invite member</Command.Item>
<Command.Item disabled>Delete workspace</Command.Item>
</Command.Group>
</Command.List>
</Command.Root>
);
}Import
import { Command } from "@meridui/react";Anatomy
<Command.Root>
<Command.Input />
<Command.List>
<Command.Empty />
<Command.Group heading="…">
<Command.Item />
</Command.Group>
<Command.Separator />
</Command.List>
</Command.Root>
<Command.Dialog>{/* the same parts */}</Command.Dialog>- Root holds the search text and the active item, filters items and handles the keys.
- Input is the search box (
role="combobox"). Focus stays in it; the active item is announced witharia-activedescendant. - List is the
role="listbox"of results. Item is an option; Group adds a heading. - Empty renders only when nothing matches. Separator hides while a search is typed.
- Dialog wraps everything in a modal Dialog that opens with ⌘K / Ctrl+K.
Examples
Command palette
Command.Dialog registers a global shortcut (default ["mod", "k"]; pass shortcut={null} to turn it off), focuses the input on open and closes after an item is chosen (closeOnSelect). This site already uses ⌘K for its own search, so the demo uses ⌘J.
Last command: nothing yet
const [open, setOpen] = useState(false);
<Button variant="secondary" onClick={() => setOpen(true)}>
Open command menu <Shortcut keys={["mod", "j"]} size="sm" />
</Button>
{/* shortcut defaults to ⌘K / Ctrl+K; this site already uses that for search. */}
<Command.Dialog
open={open}
onOpenChange={setOpen}
onSelect={(value) => run(value)}
shortcut={["mod", "j"]}
>
<Command.Input placeholder="Search pages and actions…" />
<Command.List>
<Command.Empty>Nothing matches that search.</Command.Empty>
<Command.Group heading="Pages">
<Command.Item>Home</Command.Item>
<Command.Item>Billing</Command.Item>
<Command.Item>Team</Command.Item>
</Command.Group>
<Command.Group heading="Theme">
<Command.Item value="Light theme">Light</Command.Item>
<Command.Item value="Dark theme">Dark</Command.Item>
</Command.Group>
</Command.List>
</Command.Dialog>Filtering
By default every word of the search must appear in the item's value or its keywords, ignoring case and accents ("cafe" finds "Café"). Pass filter to match differently, or shouldFilter={false} when you filter on the server and render only the matching items.
<Command.Root filter={(value, search) => value.toLowerCase().startsWith(search.toLowerCase())}>Item shortcuts
shortcut shows the keys with Shortcut and, while the command menu has focus, pressing them runs the item. Shortcuts need a modifier (⌘, Ctrl or Alt) so typing is never hijacked.
API reference
Root
Accepts every <div> attribute except onSelect.
| Prop | Type | Default | Description |
|---|---|---|---|
search | string | – | Controlled search text. |
defaultSearch | string | "" | Initial search text when uncontrolled. |
onSearchChange | (search: string) => void | – | Called when the search text changes. |
filter | (value, search, keywords) => boolean | – | Custom match function. Defaults to case- and accent-insensitive word matching. |
shouldFilter | boolean | true | Set to false to filter items yourself. |
loop | boolean | true | Wrap from the last item to the first with the arrow keys. |
onSelect | (value: string) => void | – | Called with the value of any chosen item, after the item's own onSelect. |
label | string | "Command menu" | Accessible name of the search box. |
resultsLabel | (count: number) => string | "N results" | Status announced to screen readers while searching. |
shortcutLabels | ShortcutKeyLabels | – | Spoken key names for item shortcuts, merged over the English defaults. |
ref | Ref<HTMLDivElement> | – | Forwarded ref to the root element. |
Input
Accepts every <input> attribute except value, defaultValue, onChange and type: the search text lives on Root.
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder | string | "Type a command or search…" | Placeholder text. |
onValueChange | (search: string) => void | – | Called with the new search text. |
ref | Ref<HTMLInputElement> | – | Forwarded ref to the input. |
List, Empty, Group, Separator
| Prop | Type | Default | Description |
|---|---|---|---|
List label | string | "Suggestions" | Accessible name of the listbox. |
Empty children | ReactNode | "No results found." | Shown when no item matches. |
Group heading | ReactNode | – | Visible heading; also names the group. |
Separator alwaysRender | boolean | false | Keep the separator while a search is typed. |
Item
Accepts every <div> attribute except onSelect.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | – | Unique value used for filtering and onSelect. Defaults to the text when children is a string. |
keywords | string[] | – | Extra search terms that also match. |
onSelect | (value: string) => void | – | Called when the item is chosen by click, Enter or its shortcut. |
disabled | boolean | false | Skipped by the arrow keys and ignores clicks. |
leading | ReactNode | – | Icon shown before the label. |
shortcut | string[] | – | Keys shown after the label, e.g. ["mod", "s"]; they also run the item. |
ref | Ref<HTMLDivElement> | – | Forwarded ref to the option. |
Dialog
Takes every Root prop plus:
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | – | Controlled open state. |
defaultOpen | boolean | false | Initial open state when uncontrolled. |
onOpenChange | (open: boolean) => void | – | Called when the open state should change. |
shortcut | string[] | null | ["mod", "k"] | Global key combination that toggles the dialog; null disables it. |
closeOnSelect | boolean | true | Close the dialog after an item is chosen. |
container | Element | null | document.body | Portal target. |
Styling
| Class | Element |
|---|---|
.mrd-command | Root |
.mrd-command__input-wrap, .mrd-command__input | Search row and input |
.mrd-command__list | List |
.mrd-command__group, .mrd-command__heading | Group and its heading |
.mrd-command__item | Item |
.mrd-command__leading, .mrd-command__label, .mrd-command__shortcut | Item parts |
.mrd-command__empty, .mrd-command__separator | Empty and Separator |
.mrd-command-dialog | Dialog surface |
| Attribute | On | Values |
|---|---|---|
data-active | Item | present on the active item |
data-disabled | Item | present when disabled |
data-value | Item | the item's value |
Accessibility
Follows the WAI-ARIA Combobox pattern with a listbox that is always shown. Focus never leaves the input; the active option is conveyed with aria-activedescendant, and the number of results is announced in a polite live region. When nothing matches, the list stops being a listbox and the input reports aria-expanded="false". Every announced string is a prop.
| Key | Action |
|---|---|
| ArrowDown | Next item, wrapping (skips disabled items). |
| ArrowUp | Previous item, wrapping. |
| PageUp | First item. |
| PageDown | Last item. |
| Enter | Run the active item. |
| ⌘ / CtrlK | Command.Dialog: open or close from anywhere. |
| Escape | Command.Dialog: close and return focus. |
Guidelines
Do
Name items with a verb or a destination ("New project", "Go to billing") and group them by kind.
Avoid
Make the command menu the only way to reach an action. It is a shortcut for people who know what they want.