Skip to Content
BlocksCommandPaletteBlock

CommandPaletteBlock

Search and activate application destinations and actions with keyboard support.

Plan idea 06, category Navigation & shells. Status experimental — see verification.

Import

import { CommandPaletteBlock } from "@raydenui/ui/blocks"; import type { CommandPaletteItem, CommandPaletteGroup } from "@raydenui/ui/blocks";

Usage

Each entry is a destination (href), an action (onSelect) or an entry the viewer may not use (unavailableReason). There is no arm with nothing to do.

const groups: CommandPaletteGroup[] = [ { id: "go", label: "Go to" }, { id: "do", label: "Actions" }, ]; const items: CommandPaletteItem[] = [ { id: "overview", label: "Overview", href: "/overview", groupId: "go", icon: "home" }, { id: "shipments", label: "Shipments", href: "/shipments", groupId: "go", keywords: ["freight", "consignments"] }, { id: "new", label: "New shipment", onSelect: () => openCreate(), groupId: "do", shortcut: "⌘ N" }, { id: "billing", label: "Billing", unavailableReason: "Only workspace owners can open billing." }, ]; <CommandPaletteBlock items={items} groups={groups} shortcut={{ key: "k", modifier: "mod", hint: "⌘K" }} searchPlaceholder="Search destinations and actions" />;

A destination keeps its href at every width, so middle-click and open-in-new-tab keep working. onSelect at the block level fires for every activation, including a destination followed by its href.

Suggestions before anything is typed

With no suggestions and an empty query the palette prompts for a query rather than listing everything. Set showAllWhenEmpty to list items instead.

<CommandPaletteBlock items={items} suggestions={recentDestinations} suggestionsLabel="Recent" promptTitle="What are you looking for?" promptDescription="Search destinations, actions and records." />

filterMode="local" (the default) filters items in the browser by label, description and keywords. filterMode="external" renders items untouched, for an application that searches remotely.

<CommandPaletteBlock filterMode="external" items={remoteResults} onQueryChange={(query) => search(query)} status={searching ? "loading" : "idle"} loadingMessage="Searching" />

Empty source, no matches, error

<CommandPaletteBlock items={[]} emptySourceTitle="Nothing is configured yet" emptySourceDescription="This workspace has published no destinations." noResultsTitle={(query) => `Nothing matches “${query}”`} noResultsDescription="Try a shorter term." />
<CommandPaletteBlock items={[]} status="error" errorTitle="Search is unavailable" errorDescription="The search service did not respond." onRetry={() => retry()} />

Controlled visibility and your own trigger

const [open, setOpen] = useState(false); <> <button type="button" onClick={() => setOpen(true)}> Search </button> <CommandPaletteBlock items={items} showTrigger={false} open={open} onOpenChange={setOpen} /> </>;

Props

PropTypeDefaultDescription
itemsCommandPaletteItem[]—Everything the palette can offer for the current query.
groupsCommandPaletteGroup[]—{ id, label }, resolved against item.groupId.
suggestionsCommandPaletteItem[]—Shown while the query is empty.
suggestionsLabelstring"Suggestions"Heading above suggestions.
showAllWhenEmptybooleanfalseList items instead of prompting for a query.
filterMode"local" | "external""local"Where filtering happens.
onQueryChange(query: string) => void—Query handler.
onSelect(item: CommandPaletteItem) => void—Fires for every activation.
status"idle" | "loading" | "error""idle"Lifecycle owned by your application.
errorTitlestring—Shown when status="error".
errorDescriptionstring—Shown when status="error".
onRetry() => void—The retry control is omitted without it.
loadingMessagestring—Announced while loading.
dialogLabelstring"Command palette"Accessible name for the dialog.
searchLabelstring"Search destinations and actions"Accessible name for the field.
searchPlaceholderstring—Field placeholder.
searchHintstring—Hint under the field, associated with it.
promptTitlestring—Shown before anything is typed.
promptDescriptionstring—Shown before anything is typed.
noResultsTitle(query: string) => string—Shown when the query matches nothing.
noResultsDescriptionstring—Shown when the query matches nothing.
emptySourceTitlestring—Shown when no entries are configured at all.
emptySourceDescriptionstring—Shown when no entries are configured at all.
resultCountLabel(count: number) => string—Announced and displayed result count.
showTriggerbooleantrueRenders the block’s own opening control.
triggerLabelstring"Search"Trigger label.
triggerClassNamestring—Extra classes for the built-in trigger.
shortcutCommandPaletteShortcut | nullnullKeyboard opener. null registers no document listener.
defaultOpenbooleanfalseUncontrolled initial visibility.
openboolean—Controlled visibility. Pair with onOpenChange.
onOpenChange(open: boolean) => void—Visibility handler.
closeLabelstring"Close"Close control label.
lockScrollbooleantrueFreezes the page behind the overlay.
footerReactNode—Extra content along the bottom of the dialog.
classNamestring—Lands on the container root that holds the trigger.

CommandPaletteItem

A union of three arms, all sharing { id, label, description?, icon?, keywords?, shortcut?, groupId? }:

ArmExtra membersRenders as
Destinationhref, external?, onSelect?a native anchor, at every width
ActiononSelectan activatable option
UnavailableunavailableReasona disabled entry with a visible reason

item.shortcut is a presentational string. CommandPaletteShortcut is { key, modifier?, hint? }, where modifier is "mod" (Command on Apple platforms, Control elsewhere) or "none".

States

  • prompt — nothing typed and nothing else to offer.
  • results — matching entries, grouped where groups is supplied.
  • no matches — the query matched nothing. Distinct from the prompt.
  • empty source — no entries configured at all.
  • loading / error — with an optional retry control.
  • permission-blocked entry — a disabled entry with a visible reason.
  • open / closed.

Keyboard

Arrow keys move the active option, Enter activates it, Escape closes the dialog and restores focus. A "mod" shortcut accepts Control or Command. A "none" shortcut ignores editable and contenteditable targets, composition events, repeated keys and modified keystrokes, so it never steals ordinary typing.

Verification

Measured 22 September 2026 in desktop Chromium: 261 browser tests across 27 story files, including a keyboard walk, focus containment, destinations remaining anchors, remote search, a large result set and a regression proving an unmodified shortcut does not intercept ordinary typing in either an external field or the palette’s own input. Light and dark compositions measured no document overflow at 320, 390, 768 and 1440 CSS pixels. Re-run in the repository story suite on 23 September 2026.

Not verified: a screen-reader pass, any non-Chromium engine, operating-system text-size settings, or physical devices.

Known limitations

  • The palette renders its own dialog rather than composing Modal, which hard-codes bg-white and would not follow a consumer --color-surface override. The overlay dims both light and dark pages; override --color-overlay to change its backdrop.
  • A shortcut with modifier: "none" registers a document listener. It guards editable targets and composition events, but it is still a global listener your application shares.
  • filterMode="local" matches by substring only. There is no ranking, fuzzy matching or highlighting.
Last updated on