macbetter

Menus and pickers

Command Palette

Search for an app, a document or a command and run it from the keyboard.

"use client"

import {
  Command,
  CommandInput,
  CommandPalette,
  CommandPaletteContent,
  CommandPaletteTrigger,
} from "macbetter/command-palette"
import { useState } from "react"
import { Results } from "./results"

export default function Example() {
  const [open, setOpen] = useState(false)

  return (
    <CommandPalette open={open} onOpenChange={setOpen}>
      <CommandPaletteTrigger>Search</CommandPaletteTrigger>
      <CommandPaletteContent title="Search apps and actions">
        <Command
          onAction={(value) => {
            console.log(value)
            setOpen(false)
          }}
        >
          <CommandInput aria-label="Search apps and actions" placeholder="Spotlight Search" />
          <Results />
        </Command>
      </CommandPaletteContent>
    </CommandPalette>
  )
}

When to use it

A command palette is Spotlight for your app: one field that finds any command, page or document, and runs it without a mouse. It's for people who know what they want and would rather type than browse.

  • Make it an addition, never the only way to a command.
  • Group results by kind, put the best match first, and show each command's shortcut so people learn it.
  • Open it with ⌘K, and close it as soon as a command runs.

Usage

import {
  Command,
  CommandInput,
  CommandItem,
  CommandList,
  CommandPalette,
  CommandPaletteContent,
  CommandPaletteTrigger,
} from "macbetter/command-palette"

CommandPalette is a dialog. CommandPaletteTrigger is a search-field-shaped button that also listens for ⌘K, and CommandPaletteContent is the floating panel. Inside it, Command holds a CommandInput, which draws its own magnifier, and a CommandList of groups and items. Close the palette from onAction.

On its own

Command also works inline, as a panel of its own, for a search page or a sidebar.

"use client"

import { Command, CommandInput } from "macbetter/command-palette"
import { Results } from "./results"

export default function Example() {
  return (
    <div style={{ width: "min(560px, 100%)" }}>
      <Command onAction={(value) => console.log(value)}>
        <CommandInput aria-label="Search apps and actions" placeholder="Search apps and actions" />
        <Results />
      </Command>
    </div>
  )
}

Platform behavior

  • macOS: a large translucent panel near the top of the screen, like Spotlight, with a 22-point search field, a hairline under it, and results whose highlighted row fills with the accent. Shortcuts sit at the trailing edge.
  • iOS and iPadOS: a rounded card with a capsule search field and 48-point rows. The highlight is gray, as touch highlights are. iOS hides shortcuts; iPadOS shows them.

Accessibility

The palette is a modal dialog named by title, and focus starts in the search field. Arrow keys move the highlight while focus stays in the field, Enter runs the highlighted command, Escape clears the search and then closes the palette, and focus returns to the trigger. Result counts are announced after typing pauses.

API

PropTypeDefaultDescription
CommandPaletteopen, onOpenChange—The dialog root.
CommandPaletteTriggershortcut"mod+k"Opens the palette. Shows the shortcut on the Mac and iPad.
CommandPaletteContenttitle"Search"The panel. `title` names it for screen readers.
CommandonAction, filter, autoHighlight—The search and its results.
CommandInputinput props—The search field, with its magnifier.
CommandItemvalue, label, keywords, shortcut, disabled—A result. Put a CommandShortcut inside to show its keys.
CommandItemIcon——A 22-point icon (28 on touch) before the label.
CommandGroup, CommandGroupLabel, CommandSeparator——Organize results. Empty groups disappear.
CommandEmpty, CommandStatus——What shows when nothing matches, and loading or error messages.
--mb-command-widthlength680pxThe panel's width on the Mac.
--mb-command-highlightcoloraccent · grayThe highlighted row's fill.