Skip to main content

Overview

The CommandPalette component provides a searchable command palette interface for quick navigation throughout your application. It’s triggered by keyboard shortcuts and supports fuzzy search across all accessible pages.

Features

  • Global Keyboard Shortcut: Cmd+K (Mac) / Ctrl+K (Windows/Linux)
  • Fuzzy Search: Search by page title or path
  • Keyboard Navigation: Arrow keys and Enter for selection
  • Hierarchical Display: Shows breadcrumb paths for nested resources
  • Access Control: Automatically respects route guards and permissions
  • Internationalization: Supports English and Japanese locales

Props

The CommandPalette component has no props. It automatically reads module configurations from the AppShell context.

Usage

Basic Setup

Simply add CommandPalette alongside SidebarLayout as a child of AppShell:
import { 
  AppShell, 
  SidebarLayout, 
  CommandPalette 
} from "@tailor-platform/app-shell";

const App = () => (
  <AppShell modules={modules}>
    <>
      <SidebarLayout />
      <CommandPalette />
    </>
  </AppShell>
);

With Locale Configuration

import { 
  AppShell, 
  SidebarLayout, 
  CommandPalette 
} from "@tailor-platform/app-shell";

const App = () => (
  <AppShell modules={modules} locale="ja">
    <>
      <SidebarLayout />
      <CommandPalette />
    </>
  </AppShell>
);

User Experience

Opening the Palette

Users can open the command palette by:
  • Pressing Cmd+K on macOS
  • Pressing Ctrl+K on Windows/Linux
  • Pressing the shortcut again to toggle close

Searching

  1. Type to search for pages by title or path
  2. Results update in real-time as you type
  3. Fuzzy matching helps find pages even with typos
  • Arrow Down: Move to next result
  • Arrow Up: Move to previous result
  • Enter: Navigate to selected page
  • Escape: Close the palette

Result Display

Each result shows:
  • Breadcrumb path: Module > Resource > Sub-resource hierarchy
  • URL path: The actual route path
Example:
Products > All Products > Electronics
/products/all/electronics

Search Behavior

Fuzzy Matching

The search uses fuzzy matching to find pages:
  • “prod” matches “Products”
  • “orddet” matches “Order Details”
  • “set gen” matches “Settings > General”
Search across the full breadcrumb path:
// Searching "products electronics" will find:
// Products > All Products > Electronics
Search by URL paths:
// Searching "/orders" will find:
// Dashboard > Orders (/dashboard/orders)

Access Control

The command palette automatically respects route guards:
import { defineModule, pass, hidden } from "@tailor-platform/app-shell";

const adminModule = defineModule({
  path: "admin",
  meta: { title: "Admin" },
  component: AdminPage,
  guards: [
    ({ context }) => {
      return context.currentUser?.role === "admin" ? pass() : hidden();
    }
  ],
  resources: [...]
});

// Non-admin users won't see "Admin" in command palette results

Advanced Usage

Custom Hook: useCommandPalette

For building custom command palette UIs, use the useCommandPalette hook:
import { useCommandPalette } from "@tailor-platform/app-shell";

const MyCustomPalette = ({ routes }) => {
  const {
    open,
    handleOpenChange,
    search,
    setSearch,
    selectedIndex,
    filteredRoutes,
    handleSelect,
    handleKeyDown,
    listRef,
  } = useCommandPalette({ routes });

  return (
    <Dialog open={open} onOpenChange={handleOpenChange}>
      {/* Custom UI implementation */}
    </Dialog>
  );
};

UseCommandPaletteOptions

routes
Array<NavigatableRoute>
required
Array of routes to search through. Each route has:
  • path: URL path
  • title: Page title
  • icon: Optional icon
  • breadcrumb: Array of parent titles

UseCommandPaletteReturn

open
boolean
Whether the palette is currently open
handleOpenChange
(open: boolean) => void
Function to control palette visibility
Current search query
Function to update search query
selectedIndex
number
Index of currently selected result
filteredRoutes
Array<NavigatableRoute>
Filtered and sorted search results
handleSelect
(route: NavigatableRoute) => void
Function to navigate to a selected route
handleKeyDown
(e: React.KeyboardEvent) => void
Keyboard event handler for arrow keys and Enter
listRef
React.RefObject<HTMLDivElement | null>
Ref for the results list container (for scroll management)

Helper Functions

Converts navigation items (with access control) to navigable routes:
import { navItemsToRoutes } from "@tailor-platform/app-shell";

const routes = navItemsToRoutes(navItems);
// Returns: Array<NavigatableRoute>

Internationalization

The command palette uses locale-aware strings:
  • Search placeholder: “Search…” (en) / “検索…” (ja)
  • No results: “No results found” (en) / “結果が見つかりません” (ja)
Set the locale via AppShell:
<AppShell modules={modules} locale="ja">
  <>
    <SidebarLayout />
    <CommandPalette />
  </>
</AppShell>

TypeScript

type NavigatableRoute = {
  path: string;
  title: string;
  icon?: React.ReactNode;
  breadcrumb: Array<string>;
};

UseCommandPaletteOptions Type

type UseCommandPaletteOptions = {
  routes: Array<NavigatableRoute>;
};

UseCommandPaletteReturn Type

type UseCommandPaletteReturn = {
  open: boolean;
  handleOpenChange: (open: boolean) => void;
  search: string;
  setSearch: (search: string) => void;
  selectedIndex: number;
  filteredRoutes: Array<NavigatableRoute>;
  handleSelect: (route: NavigatableRoute) => void;
  handleKeyDown: (e: React.KeyboardEvent) => void;
  listRef: React.RefObject<HTMLDivElement | null>;
};

Styling

The command palette uses Tailwind CSS with the astw: prefix. Customize appearance through CSS variables:
:root {
  --dialog-background: hsl(0 0% 100%);
  --muted: hsl(210 40% 96%);
  --accent: hsl(210 40% 96%);
}

.dark {
  --dialog-background: hsl(222 47% 11%);
  --muted: hsl(217 33% 17%);
  --accent: hsl(217 33% 17%);
}

Accessibility

  • Keyboard-first: Fully navigable with keyboard
  • Screen reader support: Proper ARIA labels and roles
  • Focus management: Auto-focuses search input on open
  • Scroll management: Selected item automatically scrolls into view
  • IME support: Ignores key events during IME composition

Performance

  • Suspense integration: Loads navigation data asynchronously
  • Memoized filtering: Optimized search performance
  • Lazy rendering: Only renders when opened

Examples

Complete Implementation

import { 
  AppShell, 
  SidebarLayout,
  CommandPalette,
  defineModule,
  defineResource
} from "@tailor-platform/app-shell";
import { Package, Settings, BarChart } from "lucide-react";

const modules = [
  defineModule({
    path: "products",
    meta: { 
      title: "Products",
      icon: <Package /> 
    },
    resources: [
      defineResource({
        path: "all",
        meta: { title: "All Products" },
        component: AllProductsPage
      }),
      defineResource({
        path: "categories",
        meta: { title: "Categories" },
        component: CategoriesPage
      })
    ]
  }),
  defineModule({
    path: "reports",
    meta: { 
      title: "Reports",
      icon: <BarChart /> 
    },
    resources: [
      defineResource({
        path: "sales",
        meta: { title: "Sales Report" },
        component: SalesReportPage
      })
    ]
  })
];

const App = () => (
  <AppShell 
    title="ERP System"
    modules={modules}
    locale="en"
  >
    <>
      <SidebarLayout />
      <CommandPalette />
    </>
  </AppShell>
);

// Users can now:
// - Press Cmd+K to open palette
// - Search "prod" to find Products pages
// - Navigate with arrow keys
// - Press Enter to navigate

See Also

Build docs developers (and LLMs) love