Skip to content
LuoForge/Tungsten
Components/CommandPalette·Experimental

CommandPalette

since v12.3.0

CommandPalette — cmd-K command menu. cmdk owns list filtering + item keyboard

nav; our Dialog owns open/scrim/focus-trap/z-index (ADR-0008).

Active row uses ink fill, never brand.

Install

Add the package and import the component.

pnpm add @hey-mike/tungsten
pnpm add @hey-mike/tungsten
import { CommandPalette } from '@hey-mike/tungsten';
import { CommandPalette } from '@hey-mike/tungsten';

Preview

Same fixtures used by the visual-regression suite.

Usage

apps/docs/app/snapshots/command-palette/page.tsx

'use client';

import { useState } from 'react';
import { Button, CommandPalette } from '@hey-mike/tungsten';
import { VariantGrid } from '../_components/VariantGrid';
import { HeroSpecimen } from '../_components/HeroSpecimen';

const items = [
  { id: 'dash', label: 'Go to dashboard', group: 'Navigation', onSelect: () => {} },
  { id: 'users', label: 'Manage users', group: 'Navigation', onSelect: () => {} },
  { id: 'invite', label: 'Invite team member', group: 'Actions', onSelect: () => {} },
  { id: 'settings', label: 'Open settings', group: 'Actions', onSelect: () => {} },
];

// Detail-page variant: a closed palette behind its trigger. The interactive
// preview opens the real (portaled, modal) palette on click. The gallery
// thumbnail uses the static mock below instead — the real open palette portals a
// full-viewport modal that would leak onto the detail page and overwhelm the
// 160px thumbnail.
function CommandPaletteDemo() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <Button onClick={() => setOpen(true)}>Open command menu (⌘K)</Button>
      <CommandPalette
        open={open}
        onOpenChange={setOpen}
        label="Command menu"
        items={items}
        placeholder="Type a command or search…"
      />
    </>
  );
}

export default function CommandPaletteSnapshot() {
  return (
    <VariantGrid
      title="CommandPalette"
      hero={
        <HeroSpecimen className="max-w-[220px]">
          <div className="border-stroke bg-surface shadow-overlay overflow-hidden rounded-md border text-left">
            <div className="border-stroke text-ink-3 border-b px-3 py-2 text-xs">
              Type a command or search…
            </div>
            <div className="p-1.5">
              <p className="text-ink-3 tracking-label px-1.5 py-1 font-mono text-2xs uppercase">
                Navigation
              </p>
              <div className="bg-surface-hover text-ink-1 rounded-sm px-1.5 py-1 text-sm">
                Go to dashboard
              </div>
              <div className="text-ink-2 rounded-sm px-1.5 py-1 text-sm">Manage users</div>
            </div>
          </div>
        </HeroSpecimen>
      }
      variants={[{ label: 'trigger', node: <CommandPaletteDemo /> }]}
    />
  );
}
'use client';

import { useState } from 'react';
import { Button, CommandPalette } from '@hey-mike/tungsten';
import { VariantGrid } from '../_components/VariantGrid';
import { HeroSpecimen } from '../_components/HeroSpecimen';

const items = [
  { id: 'dash', label: 'Go to dashboard', group: 'Navigation', onSelect: () => {} },
  { id: 'users', label: 'Manage users', group: 'Navigation', onSelect: () => {} },
  { id: 'invite', label: 'Invite team member', group: 'Actions', onSelect: () => {} },
  { id: 'settings', label: 'Open settings', group: 'Actions', onSelect: () => {} },
];

// Detail-page variant: a closed palette behind its trigger. The interactive
// preview opens the real (portaled, modal) palette on click. The gallery
// thumbnail uses the static mock below instead — the real open palette portals a
// full-viewport modal that would leak onto the detail page and overwhelm the
// 160px thumbnail.
function CommandPaletteDemo() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <Button onClick={() => setOpen(true)}>Open command menu (⌘K)</Button>
      <CommandPalette
        open={open}
        onOpenChange={setOpen}
        label="Command menu"
        items={items}
        placeholder="Type a command or search…"
      />
    </>
  );
}

export default function CommandPaletteSnapshot() {
  return (
    <VariantGrid
      title="CommandPalette"
      hero={
        <HeroSpecimen className="max-w-[220px]">
          <div className="border-stroke bg-surface shadow-overlay overflow-hidden rounded-md border text-left">
            <div className="border-stroke text-ink-3 border-b px-3 py-2 text-xs">
              Type a command or search…
            </div>
            <div className="p-1.5">
              <p className="text-ink-3 tracking-label px-1.5 py-1 font-mono text-2xs uppercase">
                Navigation
              </p>
              <div className="bg-surface-hover text-ink-1 rounded-sm px-1.5 py-1 text-sm">
                Go to dashboard
              </div>
              <div className="text-ink-2 rounded-sm px-1.5 py-1 text-sm">Manage users</div>
            </div>
          </div>
        </HeroSpecimen>
      }
      variants={[{ label: 'trigger', node: <CommandPaletteDemo /> }]}
    />
  );
}

Props

Surface specific to <CommandPalette />.

PropTypeDefaultDescription
open*boolean
onOpenChange*(open: boolean) => void
label*stringAccessible name for the palette.
items*CommandPaletteItem[]
placeholderstringType a command or search…
loadingbooleanfalse

Source