Skip to content
Areia
Esc
↑↓navigate↵open⌘Jpreview
On this page

Command

Fast, composable command menu with ranked fuzzy search, keyboard-first navigation, and smart result sorting.

A command palette or search-driven menu that filters, ranks, and reorders items as the user types. It supports grouped items, keyboard shortcuts, vim-style bindings, pointer-free keyboard navigation, flexible ranking via keywords, and keeps the original authored DOM order when no search is active.

import { Command } from "@areia/slots";

document.querySelector("#root")!.innerHTML = `
  <div data-slot="command" data-label="Command Menu" class="win95-panel w-80 p-2">
    <div data-slot="command-input-wrapper" class="mb-2">
      <input
        data-slot="command-input"
        id="command-input"
        placeholder="Search..."
        class="win95-input w-full"
      />
    </div>
    <div data-slot="command-list" class="win95-inset p-3">
      <div data-slot="command-empty" hidden class="px-3 py-1 text-neutral-600">
        No results found.
      </div>
      <div data-slot="command-group">
        <div data-slot="command-group-heading" class="win95-label">
          Actions
        </div>
        <div
          data-slot="command-item"
          data-value="copy"
          data-keywords="duplicate"
          class="win95-menu-item"
        >
          Copy
          <span
            data-slot="command-shortcut"
            class="float-right ml-8 text-neutral-700"
          >
            ⌘C
          </span>
        </div>
        <div
          data-slot="command-item"
          data-value="paste"
          data-keywords="insert"
          class="win95-menu-item"
        >
          Paste
          <span
            data-slot="command-shortcut"
            class="float-right ml-8 text-neutral-700"
          >
            ⌘V
          </span>
        </div>
      </div>
      <div data-slot="command-separator" class="win95-separator"></div>
      <div data-slot="command-group">
        <div data-slot="command-group-heading" class="win95-label">
          Navigation
        </div>
        <div data-slot="command-item" data-value="home" class="win95-menu-item">
          Home
          <span
            data-slot="command-shortcut"
            class="float-right ml-8 text-neutral-700"
          >
            ⌘H
          </span>
        </div>
        <div
          data-slot="command-item"
          data-value="settings"
          class="win95-menu-item"
        >
          Settings
          <span
            data-slot="command-shortcut"
            class="float-right ml-8 text-neutral-700"
          >
            ⌘,
          </span>
        </div>
      </div>
      <div data-slot="command-item" data-disabled class="win95-menu-item">
        Disabled item
      </div>
    </div>
  </div>
`;

const root = document.querySelector('[data-slot="command"]')!;
const controller = Command.createCommand(root, {
  label: "Command Menu",
  loop: true,
  vimBindings: true,
});

Import

import { Command } from "@areia/slots";

Usage

import { Command } from "@areia/slots";

// Auto-bind a single root
const root = document.querySelector('[data-slot="command"]');
const controller = Command.createCommand(root, {
  label: "Command Menu",
  onValueChange: (value) => console.log("active item:", value),
  onSelect: (value) => console.log("selected:", value),
});

// Auto-bind all unbound roots in scope
const controllers = Command.create();

Expected Markup

The command expects a search input and a list of items. Items can be organized into groups with headings. Use data-value for deterministic item identification, data-keywords to boost ranking, data-disabled to disable items, and data-force-mount to keep filtered-out items in the DOM.

<label for="command-input">Actions</label>
<div data-slot="command" data-label="Command Menu">
  <div data-slot="command-input-wrapper">
    <input
      data-slot="command-input"
      id="command-input"
      placeholder="Search..."
    />
  </div>
  <div data-slot="command-list">
    <div data-slot="command-empty" hidden>
      No results found.
    </div>

    <div data-slot="command-group">
      <div data-slot="command-group-heading">Actions</div>
      <div
        data-slot="command-item"
        data-value="copy"
        data-keywords="duplicate"
      >
        Copy
        <span data-slot="command-shortcut">⌘C</span>
      </div>
      <div
        data-slot="command-item"
        data-value="paste"
        data-keywords="insert"
      >
        Paste
        <span data-slot="command-shortcut">⌘V</span>
      </div>
    </div>

    <div data-slot="command-separator"></div>

    <div data-slot="command-group">
      <div data-slot="command-group-heading">Navigation</div>
      <div data-slot="command-item" data-value="home">
        Home
        <span data-slot="command-shortcut">⌘H</span>
      </div>
      <div data-slot="command-item" data-value="settings">
        Settings
        <span data-slot="command-shortcut">⌘,</span>
      </div>
    </div>

    <div data-slot="command-item" data-disabled>
      Disabled item
    </div>
  </div>
</div>

Data Slots

Slot Required Description
command Yes Root container.
command-input-wrapper No Wrapper around the search input.
command-input Yes Search input that drives filtering and ranking.
command-list Yes Scrollable container for items, groups, separators, and the empty slot.
command-item Yes Selectable result item. Value is inferred from text content or set via data-value.
command-group No Wraps a set of related items with a heading.
command-group-heading No Non-interactive heading label for a group.
command-empty No Message shown when no items match the search.
command-separator No Visual divider between groups or items.
command-shortcut No Displays a keyboard shortcut hint; excluded from search text.

Item Data Attributes

Attribute Description
data-value Explicit value used for selection and events. Falls back to inferred text content.
data-keywords Comma or newline separated extra terms that boost the item’s fuzzy-search rank without being visible in the label.
data-disabled Marks the item as non-selectable.
data-force-mount Keeps the item in the DOM even when filtered out (hidden via hidden).
data-always-render Keeps the item in the DOM and visible even when filtered out.

Generated Attributes

Attribute Element(s) Description
data-selected command-item Present on the currently active (highlighted) item.
hidden command-item, command-group Toggled based on whether the item or group matches the current search.
role command-list Set to "listbox".
role command-item Set to "option".
role command-group Set to "group".
aria-selected command-item "true" when selected, "false" otherwise.
aria-labelledby command-group Points to the group heading id.
aria-disabled command-item "true" when the item is disabled.

Events

Outbound

Event Detail Description
command:select { value: string } Fired on the root when an item is selected via click or Enter.
command:change { value: string } Fired on the root when the active (highlighted) item changes.

Inbound

Event Detail Description
command:set { value: string } Dispatch on the root to programmatically set the active item.

API Reference

Options

Option Type Default Description
defaultValue string — Initial active item value.
label string — Accessible label announced to assistive technology. Defaults to the data-label attribute on the root.
shouldFilter boolean true Disable built-in filtering and sorting when set to false.
loop boolean false Wrap arrow-key navigation from last to first item and vice versa.
disablePointerSelection boolean false Ignore pointer-move events for selection, keeping interaction keyboard-only.
vimBindings boolean true Enable Ctrl+J, Ctrl+K, Ctrl+N, and Ctrl+P shortcuts for vertical navigation.
filter (value: string, search: string, keywords?: string[]) => number — Custom ranking function. Return 0 to hide the item. Higher scores rank higher.
onValueChange (value: string | null) => void — Called when the active item value changes.
onSearchChange (search: string) => void — Called when the search query changes.
onSelect (value: string) => void — Called when an item is selected via click or Enter.

Controller

Member Type Description
value string | null (getter) Current active item value.
search string (getter) Current search query.
select(value) (value: string | null) => void Set the active item value programmatically. Passing null clears the selection.
setSearch(value) (search: string) => void Set the search query programmatically.
destroy() () => void Remove all listeners, mutation observers, and clean up.

Controller

The controller is returned by Command.createCommand() and provides imperative control over the command menu’s state.

const controller = Command.createCommand(root, {
  label: "Command Menu",
});

// Set search query programmatically
controller.setSearch("copy");

// Select an item programmatically
controller.select("paste");

// Clear selection
controller.select(null);

// Read current state
console.log(controller.value); // "paste" | null
console.log(controller.search); // "copy"

// Clean up
controller.destroy();

Was this page helpful?