Skip to content

Command Menu

A command menu puts every action behind one shortcut. Press Cmd+K, or Ctrl+K on Windows, type a few letters, press Enter. Nobody has to remember where a setting lives, only roughly what it does.

Open it instantly, match what people mean, and show each command's shortcut next to it.

KS
  • Inbox
  • Projects
  • Team

Projects

  • Website redesign
  • Q4 launch
  • Mobile app
Click the window, then press ⌘K

Click the window and press the shortcut, or use the search field. Arrows move, Enter runs, Escape closes. Run "Toggle sidebar" from the menu and the footer tells you the shortcut for next time.

Open it instantly

The menu exists to make things fast, so it can't be slow to appear. It opens with no animation, the cursor is already in the input, and the same shortcut closes it. This is hover restraint again: something you open forty times a day can't afford a fade.

Closing returns focus to the search field, and nothing in the list needs a mouse.

Match what people mean

People don't type your label, they type their own word. Someone looking for dark mode types "dark", not "Change theme". An exact search finds nothing, and they decide the feature doesn't exist.

Try
Exact title
No results
Keywords
  • Change themedark mode

Give every command a few keywords and match loosely against the title and the keywords together. When a keyword matched, show it next to the result so the match doesn't look random.

Teach the shortcuts

A command menu is also where people learn your shortcuts. If every row shows its keys, the tenth time someone runs "Toggle sidebar" from the menu, they press the shortcut instead. A shortcut nobody sees never gets learned.

  • Change theme
  • New project
  • Toggle sidebar
  • Archive project
  • Invite teammate

Order the list for the person, not the codebase. With an empty query, show recent commands first, then the ones that fit the current screen. Most of the time, the thing someone wants is the thing they did a minute ago.

Usage

app.tsx
import { Command } from "cmdk";
import { useEffect, useState } from "react";

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

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

  return (
    <Command.Dialog open={open} onOpenChange={setOpen} label="Command menu">
      <Command.Input placeholder="Type a command" />
      <Command.List>
        <Command.Empty>No commands found.</Command.Empty>
        <Command.Item
          value="Change theme"
          keywords={["dark mode", "light mode", "appearance"]}
          onSelect={toggleTheme}
        >
          Change theme
          <kbd className="ml-auto text-xs text-muted-foreground">⌘⇧L</kbd>
        </Command.Item>
      </Command.List>
    </Command.Dialog>
  );
}
<div
  cmdk-item
  class="flex h-8 items-center gap-2.5 rounded-lg px-2.5 text-sm data-[selected=true]:bg-muted"
>
  Change theme
  <kbd class="ml-auto text-xs text-muted-foreground">⌘⇧L</kbd>
</div>

Set value on every item. Without it, cmdk uses the item's text, so the shortcut becomes part of what it searches and "L" starts matching "Change theme".

Resources

Search concepts

Search for a command to run...