Skip to content
oks-ui

App screens

Command palette shell

The command-menu pattern in the same persistent sidebar/header shell as the other App screens patterns: a search-style Button in the header opens a real CommandPalette (also bound to ⌘/Ctrl+K via useCommandPalette), with grouped, icon-labeled, keyword-searchable commands and a recent-items view when the query is empty.

command-palette-shell.tsx

How it's built

1

useCommandPalette wires the ⌘/Ctrl+K hotkey and open state; spread getPaletteProps() onto CommandPalette — the header's search Button just calls the same palette.open().

2

emptyQueryItems shows a curated "recent" list before the visitor types anything; once they type, the built-in fuzzy filter takes over and matches keywords beyond the visible label (try "bill").

3

The dialog traps focus, restores it to whichever control opened it (the header button or the ⌘K hotkey) on close, and closes on Escape.

Source

One file · copy & paste
command-palette-shell.tsx
import { useState } from "react";
import { Button } from "oks-ui/button";
import { CommandPalette, useCommandPalette } from "oks-ui/command-palette";
import type { CommandPaletteItem } from "oks-ui/command-palette";
import type { NavItemData } from "oks-ui/nav";
import { PatternAppShell } from "../PatternAppShell";
import {
  BriefcaseIcon,
  CreditCardIcon,
  GridIcon,
  HelpCircleIcon,
  PlusIcon,
  ReceiptIcon,
  SearchIcon,
  SettingsIcon,
  UsersIcon,
} from "../../showcase/icons";

// A dashed square with a plus — "invite teammate".
function UserPlusIcon({ size = 16 }: { size?: number }) {
  return (
    <svg width={size} height={size} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <circle cx="6.5" cy="6" r="2.5" />
      <path d="M2 14c0-2.5 2-4 4.5-4s4.5 1.5 4.5 4" />
      <path d="M13 5.5v4M11 7.5h4" />
    </svg>
  );
}

// A downward-pointing arrow into a tray — "export".
function ExportIcon({ size = 16 }: { size?: number }) {
  return (
    <svg width={size} height={size} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M8 2.5v7M5 6.5 8 9.5l3-3" />
      <path d="M2.5 12v1.5A1.5 1.5 0 0 0 4 15h8a1.5 1.5 0 0 0 1.5-1.5V12" />
    </svg>
  );
}

// A moon — "toggle theme".
function MoonIcon({ size = 16 }: { size?: number }) {
  return (
    <svg width={size} height={size} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M13.5 9.5a5.5 5.5 0 1 1-7-7 4.3 4.3 0 0 0 7 7Z" />
    </svg>
  );
}

const NAV_ITEMS: NavItemData[] = [
  {
    key: "main",
    label: "Main menu",
    isSection: true,
    children: [
      { key: "dashboard", label: "Dashboard", icon: <GridIcon size={18} /> },
      { key: "projects", label: "Projects", icon: <BriefcaseIcon size={18} /> },
      { key: "invoices", label: "Invoices", icon: <ReceiptIcon size={18} /> },
      { key: "team", label: "Team", icon: <UsersIcon size={18} /> },
    ],
  },
  {
    key: "other",
    label: "Other",
    isSection: true,
    children: [
      { key: "billing", label: "Billing", icon: <CreditCardIcon size={18} /> },
      { key: "settings", label: "Settings", icon: <SettingsIcon size={18} /> },
    ],
  },
];

const ITEMS: CommandPaletteItem[] = [
  { id: "new-invoice", label: "Create invoice", group: "Actions", icon: <ReceiptIcon size={16} />, shortcut: ["⌘", "N"], keywords: ["bill", "new"] },
  { id: "invite", label: "Invite teammate", group: "Actions", icon: <UserPlusIcon />, keywords: ["team", "member"] },
  { id: "export", label: "Export data as CSV", group: "Actions", icon: <ExportIcon />, keywords: ["download"] },
  { id: "theme", label: "Toggle dark mode", group: "Actions", icon: <MoonIcon />, keywords: ["theme", "appearance"] },
  { id: "dashboard", label: "Go to Dashboard", group: "Navigation", icon: <GridIcon size={16} />, keywords: ["home", "overview"] },
  { id: "projects", label: "Go to Projects", group: "Navigation", icon: <BriefcaseIcon size={16} /> },
  { id: "team", label: "Go to Team", group: "Navigation", icon: <UsersIcon size={16} /> },
  { id: "billing", label: "Go to Billing & plans", group: "Navigation", icon: <CreditCardIcon size={16} /> },
  { id: "settings", label: "Open Settings", group: "Navigation", icon: <SettingsIcon size={16} /> },
  { id: "help", label: "Search the help center", group: "Support", icon: <HelpCircleIcon size={16} /> },
];

const RECENT_IDS = ["dashboard", "new-invoice", "team"];

export function CommandPaletteShell() {
  const [lastAction, setLastAction] = useState<string | null>(null);
  const palette = useCommandPalette();

  const recentItems = ITEMS.filter((item) => RECENT_IDS.includes(item.id));

  return (
    <>
      <PatternAppShell
        navItems={NAV_ITEMS}
        selectedNavKey="dashboard"
        headerLeft={
          <Button
            variant="bordered"
            size="sm"
            onClick={palette.open}
            startContent={<SearchIcon size={15} />}
            className="w-full max-w-xs justify-between text-ink-faint"
            endContent={
              <span className="flex items-center gap-0.5 rounded-[var(--r-sm)] bg-paper-sunken px-1.5 py-0.5 text-[0.7rem] font-medium text-ink-faint">
                ⌘K
              </span>
            }
          >
            Search or jump to…
          </Button>
        }
        contentClassName="flex flex-1 flex-col items-center justify-center gap-4 overflow-y-auto p-8 text-center"
      >
        <span className="flex h-14 w-14 items-center justify-center rounded-[var(--r-lg)] bg-[color:var(--oks-color-primary-50)] text-[color:var(--oks-color-primary-700)]">
          <SearchIcon size={24} />
        </span>
        <div>
          <p className="text-[1rem] font-semibold text-ink">Find anything, instantly</p>
          <p className="mt-1 max-w-xs text-[0.85rem] text-ink-muted">
            Press <kbd className="rounded-[var(--r-sm)] bg-paper-sunken px-1.5 py-0.5 font-sans text-[0.8rem]">⌘K</kbd> or the search bar above to jump to a
            page, run an action, or search for help.
          </p>
        </div>
        <Button color="primary" radius="full" startContent={<PlusIcon size={14} />} onClick={palette.open}>
          Open command palette
        </Button>
        {lastAction ? <p className="text-[0.8rem] text-ink-faint">Last ran: {lastAction}</p> : null}
      </PatternAppShell>

      <CommandPalette
        {...palette.getPaletteProps()}
        items={ITEMS}
        emptyQueryItems={recentItems}
        placeholder="Search commands, pages, and help…"
        onSelect={(item) => setLastAction(item.label)}
      />
    </>
  );
}

More patterns

App screens
Analytics dashboardA full fintech app screen — collapsible sidebar, header search, wallet cards, an earnings chart, savings goals, and a transactions table.
App screens
Settings panelA real settings screen — top tabs, two-column sections, and a sortable, selectable billing history table.
App screens
Data table viewA full documents list screen — status Tabs, a filter toolbar, and a sortable, selectable Table with real recipient avatars.