Command Palette
Terminal-styled command palette with built-in search filtering, grouped items, and keyboard navigation. Uses native dialog element with backdrop blur. Two orthogonal visual axes on Content (frame and density) keep visual chrome configurable without touching internals.
Installation
dgadd is not public on npm yet. Until the first release, pack @diffgazer/add from the repository and install that tarball into this app, which is what puts dgadd on pnpm exec.
The hosted registry is not public yet because r.b4r7.dev does not resolve. Use this source checkout or a local registry preview until the endpoint returns 200.
Diffgazer packages are not yet published to npm. Until the first release, pack @diffgazer/ui and @diffgazer/keys from the repository and install those tarballs.
UI components require Tailwind CSS v4. Local copy mode imports src/styles/styles.css; package mode uses @diffgazer/ui CSS once packages are available.
Usage
Examples
Viewfinder frame
Terminal frame
Comfortable density
Dense density
Tones (manual)
Tones (auto-coloring)
Empty state
API Reference
CommandPalette
CommandPaletteContent
CommandPaletteItem
CommandPaletteGroup
CommandPaletteInput
Data attributes
Accessibility
Keyboard Navigation
Arrow keys navigate items (with wrapping), Enter activates the highlighted item. Home and End retain their native search-input editing behavior and do not move the highlight. Escape clears search first, then closes the palette. Hovering (mousemove) over an item also moves the highlight, so mouse and keyboard share a single selection model. Navigation is handled internally via @diffgazer/keys's useNavigation hook.
| Key | Action |
|---|---|
| ArrowUp / ArrowDown | Moves highlight through enabled visible items. |
| Enter | Activates the highlighted item. |
| Escape | Clears the search query first, then closes the palette. |
Keyboard navigation
Notes
Requires @diffgazer/keys (package mode)
CommandPalette's arrow-key navigation and focus restoration import from the required @diffgazer/keys peer. Package consumers must install @diffgazer/keys with @diffgazer/ui; packages are not yet published to npm, so until the first release install both from locally packed tarballs. Importing @diffgazer/ui/components/command-palette without keys fails at module load with an error naming the missing @diffgazer/keys package. Copy/dgadd consumers do not need the package — copy mode rewrites the keyboard hooks to local source.
Controlled Open State
CommandPalette is opened from outside via open/onOpenChange. Wire a trigger button (or a global keyboard shortcut) to setOpen(true). Search and highlight state can also be controlled via search/onSearchChange and highlighted/onHighlightChange, or left uncontrolled.
Built-in Filtering
Items are filtered automatically as you type. Each item matches against its `value` prop (falls back to `id`). Pass `shouldFilter={false}` to disable and handle filtering yourself. Pass a custom `filter` function to override the default case-insensitive includes match. Filtering uses the live search value, including the controlled `search` prop when provided.
Composition Contract
Use CommandPalette.Item as an explicit child in the CommandPalette JSX tree, usually inside CommandPalette.List or CommandPalette.Group. Custom item UI belongs inside CommandPalette.Item. Components that create items internally from an opaque wrapper are not part of the current public contract.
Built-in Keyboard Navigation
CommandPalette integrates @diffgazer/keys's useNavigation internally for arrow-key navigation, wrapping, and Enter activation. Hover (mousemove) and keyboard selection share the same highlighted state, so mousing over an item moves the cmdk-style cursor. Highlight and search state can be controlled externally via highlighted/onHighlightChange and search/onSearchChange.
Variants & Density
CommandPaletteContent exposes two orthogonal axes. `frame` picks the shell chrome — "border" (1px hairline, default), "viewfinder" (no border, four corner brackets + 2px left accent bar on selection), "terminal" (top + bottom 2px rules, inverted selection, prefix glyph becomes $), "card" (rounded 8px shell with a subtle gradient surface and floating rounded selection — Linear-ish), or "none" (bare shell for embedding). `density` picks the typographic & spacing surface — "compact" (default), "comfortable", or "dense". Both are plain TypeScript types (CommandPaletteFrame, CommandPaletteDensity) whose visual styling is driven by [data-frame] / [data-density] selectors in command-palette/command-palette.css, so consumers can override token values per-instance via CSS custom properties.
Modal or Embedded
By default Content renders a native modal dialog in the browser top layer with a focus trap and focus restoration. Pass modal={false} to embed the same surface in the page: identical frame, density, tone, and highlight chrome, but in the document flow, without a backdrop and without stealing focus - useful for a persistent search pane, and for documenting the open surface itself. The examples below are embedded palettes for exactly that reason.
Keyboard legend
CommandPaletteFooter renders the shared OverlayHints primitive (registry/ui/shared/overlay-hints) when given no children: Navigate / Select / Close, in one layout every keyboard-first overlay in the library speaks. Pass children to take full control. The legend is aria-hidden — the shortcuts are already reachable through the real controls — and at coarse pointer every hint that is not marked touch-relevant is hidden, collapsing the whole bar rather than leaving an empty strip; on a 390-wide palette that recovers a full row, and the working close affordance is the Esc button in the input row.
Narrow-viewport geometry
Below 640px the panel insets 12px from each viewport edge (max-sm:mx-3 plus a matching width) so both vertical hairlines and the offset shadow stay on-screen instead of being clipped. The panel is top-pinned at every width — margin-block-start: max(12px, env(safe-area-inset-top)) with an auto bottom margin — so the software keyboard shrinking the visual viewport cannot displace the input row mid-typing. The height cap stays max-h-[80dvh]. The footer pads past the home indicator with env(safe-area-inset-bottom), which needs viewport-fit=cover on the host page and degrades to the base padding without it. The Esc affordance in the input row is a real button with the accessible name "Close" and a 44x44 hit area expanded via ::before, so touch users have a working close control; the visible chip does not move.
Position readout
The default input suffix renders CommandPaletteCount: a bracketed readout of the highlighted position over the filtered total — [3/24] with a highlight, [24] without one, and [0] with a data-empty marker (error colour) when the filter matched nothing. It is aria-hidden, because the palette's existing polite live region already announces the result count; rendering it visibly is what closes the gap for sighted keyboard users. Supplying your own `suffix` replaces it, so compose CommandPalette.Count yourself if you want both.
Optional auto-coloring
Items accept a `tone` prop ("neutral" | "nav" | "action" | "settings" | "destructive" | "ai") that renders a 2px left accent bar and tints the optional icon. The label color is unchanged so contrast remains readable, including under the terminal frame's inverted selection. For automatic classification + inline match highlighting, import `CommandPaletteHighlightItem` from `@diffgazer/ui/components/command-palette/highlight`. It infers tone from a small regex table (verbs like "delete", "go to", "toggle", "ask", "run") and wraps matched characters in `<mark data-slot="command-palette-item-match">`.
Source
Install via CLI: pnpm exec dgadd add ui/command-palette. Keyboard hooks are included as standalone copies. For the full experience, use --integration keys.
Highlighted source loads after this disclosure opens. Browse the source repository.