Accent theme

Base radius

Accent theme

Base radius

Colour scheme
Components

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.

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/react";
import type { CommandPaletteItem } from "@kernelui/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} />
    </>
  );
}

Props

PropTypeDefault
openboolean
onOpenChange(open: boolean) => void
itemsCommandPaletteItem[]
placeholderstring"Filter commands"
emptyMessageReactNode"No results"

CommandPaletteItem: { id, label, description?, onSelect }.

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.