GitHubnpm

Accent theme

Base radius

GitHubnpm

Accent theme

Base radius

Colour scheme

Docs menu

Guides


Primitives
Forms
Layout
Feedback
Overlays
Navigation
Data Display
AI

Command Palette

A real <dialog>, opened with showModal(), the same reasoning as Dialog: a native top-layer stacking context, a native focus trap, and native Escape-to-close, none of it reimplemented in JavaScript. It's controlled, not self-triggering, you decide what opens it, a button, a keyboard shortcut, whatever, and it stays generic: it takes a flat list of items, it doesn't know anything about your app's navigation or commands. Open and close fade the panel and backdrop with a slight scale + settle by default — Escape and backdrop dismiss wait for the exit transition, same as Dialog — andprefers-reduced-motion collapses that to a short opacity fade.

No results
New file
Create a new file
New folder
Create a new folder
Save
Save the current file
Rename
Rename the selected item
Delete
Delete the selected item
Close tab
Close the current tab
Usage
import { useState } from "react";
import { Button, CommandPalette } from "@kernelui-lib/react";
import type { CommandPaletteItem } from "@kernelui-lib/react";

function App() {
  const [open, setOpen] = useState(false);

  const items: CommandPaletteItem[] = [
    { id: "new-file", label: "New file", onSelect: () => {} },
    { id: "save", label: "Save", description: "Save the current file", onSelect: () => {} },
  ];

  return (
    <>
      <Button onClick={() => setOpen(true)}>Open command palette</Button>
      <CommandPalette open={open} onOpenChange={setOpen} items={items} />
    </>
  );
}

Playground

Toggle every adjustable prop and watch the component — and the code — update live.

No results
New file
Save
Save the current file
Delete
Unstyled

Same component — Kernel look stripped. See Platforms.

blur
Usage
<Button onClick={() => setOpen(true)}>Open command palette</Button>
<CommandPalette
  open={open}
  onOpenChange={setOpen}
  items={items}
  placeholder="Filter commands"
  emptyMessage="No results"
/>

Grouping & custom rendering

Pass groups instead of a flat items array to section the results under labelled headers, "Recent", "File", a danger zone, whatever fits your commands. Filtering, Arrow Up/Down, and aria-activedescendant all operate across the flattened result set. renderItem replaces an item's default label/description with anything you want, given the item, whether it's active, and which group (if any) it belongs to.

No results
Save
Save the current file
New file
Create a new file
New folder
Create a new folder
Delete
Delete the selected item
Usage
import { useState } from "react";
import { CommandPalette } from "@kernelui-lib/react";
import type { CommandPaletteGroup, CommandPaletteItem } from "@kernelui-lib/react";

const groups: CommandPaletteGroup[] = [
  { id: "recent", label: "Recent", items: [
    { id: "save", label: "Save", onSelect: () => {} },
  ] },
  { id: "file", label: "File", items: [
    { id: "new-file", label: "New file", onSelect: () => {} },
  ] },
  { id: "danger", label: "Danger zone", items: [
    { id: "delete", label: "Delete", onSelect: () => {} },
  ] },
];

function renderItem(item: CommandPaletteItem, state: { group?: CommandPaletteGroup }) {
  return (
    <div style={{ color: state.group?.id === "danger" ? "var(--kernel-color-danger)" : undefined }}>
      {item.label}
    </div>
  );
}

function App() {
  const [open, setOpen] = useState(false);

  return (
    <CommandPalette
      open={open}
      onOpenChange={setOpen}
      groups={groups}
      renderItem={renderItem}
    />
  );
}

Composable async results

The compound API keeps one focused input while your product owns the result model. This example combines controlled query state, external filtering, grouped lesson and section rows, active render state, disabled results, loading, empty, error, and asynchronous replacement.

Course search

Find a lesson or jump straight to a matching section.

3 lessons indexed
Flexbox that holds up under pressureFree lesson
Module 04: Resilient layout
The two-axis modelUnderstand how main and cross axes change when direction changes.
Gaps, not magic marginsUse gap to keep spacing predictable as items wrap.
Motion that explains the interfaceCompleted
Module 07: Physical feedback
Anticipation and releaseA good transition prepares the eye before the layout changes.
Typography as a layout systemPremium
Module 02: Type and rhythm
Usage
import { CommandPalette, Combobox } from "@kernelui-lib/react";

<CommandPalette open={open} onOpenChange={setOpen} shouldFilter={false}>
  <CommandPalette.Input value={query} onValueChange={setQuery} />
  <CommandPalette.List>
    {isLoading && <CommandPalette.Loading>Searching...</CommandPalette.Loading>}
    {!isLoading && error && <CommandPalette.Empty>{error}</CommandPalette.Empty>}
    <CommandPalette.Group heading="Engineering Track">
      <CommandPalette.Item id="lesson-flexbox" value={lesson.searchText} onSelect={goToLesson}>
        {({ active }) => <LessonRow lesson={lesson} active={active} />}
      </CommandPalette.Item>
      <CommandPalette.Item id="section-flexbox-layout" value={section.searchText} onSelect={goToSection}>
        {({ active }) => <SectionRow section={section} active={active} />}
      </CommandPalette.Item>
    </CommandPalette.Group>
  </CommandPalette.List>
</CommandPalette>

Props

Props for Command Palette.
PropTypeDefault
openboolean
onOpenChange(open: boolean) => void
itemsCommandPaletteItem[]
groups{ id, label?, items: CommandPaletteItem[] }[]
renderItem(item, { active, group?, index }) => ReactNode
placeholderstring"Filter commands"
emptyMessageReactNode"No results"
blurbooleanfalse

CommandPaletteItem: { id, label, description?, onSelect }. Provide either items or groups — when both are set, groups wins. Each group needs a unique id; a group without a label renders its items without a header.

Accessibility

  • Follows the WAI-ARIA combobox pattern: the filter input is a real role="combobox", focus never leaves it, and aria-activedescendant points at whichever role="option" is highlighted.
  • Arrow Up/Down move the active option, clamped at the ends. Enter selects it and closes the palette. Escape closes it too, natively, it isn't handled twice.
  • Filtering is a case-insensitive substring match against each item's label, live as you type.
  • Clicking the ::backdrop closes the dialog.
  • Grouped items are wrapped in role="group", labelled via aria-labelledby by a role="presentation" header, so assistive tech announces which group an item belongs to.