General · astro-ui · Command

CommandJS

Inline searchable command palette — grouped actions, keyboard-first.

Command renders a framed palette with a search input on top of a scrollable, grouped action list. Type to filter (case-insensitive substring on labels and keywords), move with ↑/↓, run with Enter or click. Items are declarative: pass ordered groups of { value, label, hint?, shortcut?, disabled?, keywords? } — shortcuts render Kbd-style on the right, hints as a muted second line. Selection emits a bubbling command:select event with the item’s value; run your action from a listener. loop wraps highlighting, vimBindings adds j/k and ctrl+n/ctrl+p, and shouldFilter={false} hands filtering to you for async sources.

Features

  • Grouped, ordered sections with uppercase group headings
  • Case-insensitive filter across labels + keywords; per-item disabled state
  • Full keyboard support: ↑/↓ navigate, Enter runs, Home/End jump; optional loop wrap
  • Right-aligned Kbd-style shortcuts (⌘P) and muted hint lines per item
  • vimBindings: j/k + ctrl+n/ctrl+p navigation
  • Bubbling command:select and command:search-change events
  • shouldFilter={false} for async/external filtering

Usage

---
import { Command } from "astro-ui";
---

<Command
  label="Commands"
  groups={[
    { label: "Suggestions", items: [
      { value: "calendar", label: "Calendar" },
      { value: "calculator", label: "Calculator", disabled: true },
    ]},
    { label: "Settings", items: [
      { value: "profile", label: "Profile", shortcut: "⌘P" },
    ]},
  ]}
/>

<script>
  document.addEventListener("command:select", (e) => {
    console.log(e.detail.value);
  });
</script>

Demos

Basic

Suggestions

Settings

<Command
  label="Commands"
  style="max-inline-size:20rem"
  groups={[
    { label: "Suggestions", items: [
      { value: "calendar", label: "Calendar" },
      { value: "emoji", label: "Search Emoji" },
      { value: "calculator", label: "Calculator", disabled: true },
    ]},
    { label: "Settings", items: [
      { value: "profile", label: "Profile", shortcut: "⌘P" },
      { value: "billing", label: "Billing", shortcut: "⌘B" },
      { value: "settings", label: "Settings", shortcut: "⌘S" },
    ]},
  ]}
/>

<script>
  document.addEventListener("command:select", (e) => {
    console.log("run:", e.detail.value);
  });
</script>

Hints & keywords

Repository

<Command
  label="Actions"
  style="max-inline-size:22rem"
  groups={[
    { label: "Repository", items: [
      { value: "push", label: "Push to origin", hint: "main → origin/main", keywords: ["git", "upload"] },
      { value: "pr", label: "Open pull request", hint: "3 files changed", keywords: ["git", "review"] },
      { value: "stash", label: "Stash changes", keywords: ["git", "save"] },
    ]},
  ]}
/>

Loop + vim bindings

Actions

<Command
  label="Commands"
  loop
  vimBindings
  autoHighlight
  style="max-inline-size:20rem"
  groups={[{ label: "Actions", items: [
    { value: "one", label: "First action" },
    { value: "two", label: "Second action" },
    { value: "three", label: "Third action" },
  ]}]}
/>
<!-- loop wraps the highlight; vimBindings adds j/k + ctrl+n/ctrl+p;
     autoHighlight lands on the first item immediately -->

API

Props

PropTypeDefaultDescription
groups{ label, items }[]requiredOrdered sections; items are { value, label, hint?, shortcut?, disabled?, keywords? }.
labelstring"Commands"Accessible name for the palette.
defaultSearchstring""Initial text in the search field.
placeholderstring"Type a command or search…"Search placeholder text.
emptyMessagestring"No results found."Shown when nothing matches.
shouldFilterbooleantrueClient-side filter; disable for async filtering.
loopbooleanfalseHighlight wraps from end back to start.
vimBindingsbooleanfalsej/k and ctrl+n/ctrl+p move the highlight.
autoHighlightbooleanfalseHighlight the first item immediately.
classstring—Merged via cn(); Tailwind classes welcome.
idstring—Element id.

Accessibility

Implemented accessibility features — semantics, ARIA wiring, and keyboard behavior that ship with the component.

  • Root is a labelled group; the search input carries role=combobox with aria-controls and aria-autocomplete=list
  • Listbox carries role=listbox; items carry role=option and aria-selected managed by the script
  • Disabled items are aria-disabled and skipped by both navigation and Enter
  • aria-activedescendant keeps focus in the search field while navigating
  • Empty state lives inside the listbox so it is announced with context

FAQ

How is this different from Combobox?

Combobox commits a form value — it has a hidden input and posts with the form. Command runs actions: items are buttons that emit command:select, and nothing is submitted. Use Combobox to pick one of many values, Command to trigger one of many actions.

Can it open in a dialog?

Yes — place Command inside Dialog content and open the dialog from a keyboard shortcut or button. The palette is inline by design, so composition stays explicit.

Changelog

Version history mentioning Command.

v0.1.0 — 2026-11-11
added
  • General (7): Button, ButtonGroup, Command, Carousel, Clipboard, Kbd, Link.