Command
hc-command is a command palette, the shadcn Command / cmdk
equivalent. It is the WAI-ARIA combobox pattern used as an action
launcher: type to filter a grouped list, arrow to highlight, Enter to
run. installCommand wires the filtering, aria-activedescendant
keyboard navigation, the selection event, and an optional ⌘K opener.
Use it inside a native <dialog> for the classic centred palette — you
get focus trapping, Escape-to-close, and a backdrop for free — or
inline.
<button type="button" class="hc-button" onclick="this.nextElementSibling.showModal()"> Open command palette</button>
<dialog class="hc-command-dialog" data-hotkey="k"> <div class="hc-command"> <input class="hc-command__input" type="text" role="combobox" autofocus aria-label="Command menu" placeholder="Type a command…"> <div class="hc-command__list" role="listbox"> <div class="hc-command__group" role="group" aria-labelledby="g-nav"> <div class="hc-command__group-heading" id="g-nav">Navigation</div> <div class="hc-command__item" role="option" data-value="home"> <span>Go home</span> <kbd class="hc-command__shortcut">G H</kbd> </div> <div class="hc-command__item" role="option" data-value="profile"> <span>Open profile</span> </div> </div> </div> <div class="hc-command__empty" hidden>No results.</div> </div></dialog>Markup
Section titled “Markup”<dialog class="hc-command-dialog" data-hotkey="k"> <div class="hc-command"> <input class="hc-command__input" type="text" role="combobox" autofocus aria-label="Command menu" placeholder="Type a command…"> <div class="hc-command__list" role="listbox"> <div class="hc-command__group" role="group" aria-labelledby="g-nav"> <div class="hc-command__group-heading" id="g-nav">Navigation</div> <div class="hc-command__item" role="option" data-value="home"> <span>Go home</span> <kbd class="hc-command__shortcut">G H</kbd> </div> <div class="hc-command__item" role="option" data-value="profile"> <span>Open profile</span> </div> </div> </div> <div class="hc-command__empty" hidden>No results.</div> </div></dialog>The list uses role="listbox" with role="option" items grouped under
role="group" headings (the cmdk / Radix structure). Each item’s
data-value is what the select event reports and what the filter
matches against (the .hc-command__shortcut text is excluded from the
match).
JavaScript
Section titled “JavaScript”import { installCommand } from '@hypermedia-components/core';installCommand(); // idempotent; returns an uninstallerThe zero-config @hypermedia-components/core/behaviors entry installs
it automatically.
Opening with ⌘K
Section titled “Opening with ⌘K”Put data-hotkey="k" on the <dialog> (any single key;
default k) and the behavior toggles it with ⌘/Ctrl
- the key, focusing the input and resetting the filter on open:
<dialog class="hc-command-dialog" data-hotkey="k">…</dialog>The handler calls preventDefault() so the browser’s own
Ctrl+K shortcut does not also fire. You can still
open the dialog from a button with dialog.showModal() — autofocus
on the input puts the cursor in the search box.
Filtering & ranking
Section titled “Filtering & ranking”Typing fuzzy-filters and re-ranks the items: the query characters must appear in order (a subsequence), and a match scores higher when its characters are contiguous or land on a word / camelCase boundary. Items reorder by score — the best match floats to the top, even across groups, and ties keep the authored order. Clearing the query restores the original sequence. Filtering is always client-side; the palette never touches the network.
To keep the previous plain behaviour — a case-insensitive substring match
with no reordering — set data-filter="substring" on the .hc-command:
<div class="hc-command" data-filter="substring">…</div>The .hc-command__shortcut text is excluded from the match (only the item’s
label is searched).
Keyboard
Section titled “Keyboard”| Key | Action |
|---|---|
| Type | Fuzzy-filter and re-rank items; empty groups and their headings hide; an empty state shows when nothing matches. |
↓ / ↑ | Move the highlight (wraps; skips disabled items). |
Home / End | First / last visible item. |
Enter | Run the highlighted item. |
Escape | Close (native <dialog>). |
DOM focus stays on the input; the highlighted item is tracked with
aria-activedescendant, per the WAI-ARIA combobox pattern.
The select event
Section titled “The select event”Running an item dispatches a bubbling hc:commandselect on the
.hc-command root and (when inside a <dialog>) closes it:
command.addEventListener('hc:commandselect', (e) => { const { item, value } = e.detail; // value = item's data-value routeTo(value);});htmx usage
Section titled “htmx usage”Run an action server-side straight off the event:
<div class="hc-command" data-hx-post="/commands/run" data-hx-trigger="hc:commandselect" data-hx-vals='js:{ command: event.detail.value }'> …</div>Hyperscript
Section titled “Hyperscript”Run the chosen command inline instead of wiring JS:
<div class="hc-command" _="on hc:commandselect call runCommand(event.detail.value)"> …</div>More patterns: Hyperscript → Reacting to component events.
Accessibility
Section titled “Accessibility”- The input is
role="combobox"witharia-expanded,aria-controls, andaria-activedescendant; the list isrole="listbox", groups arerole="group"witharia-labelledbyheadings, items arerole="option". - Inside a
<dialog>opened withshowModal(), focus is trapped andEscapecloses — both native. Keepautofocuson the input so the search field is ready on open. - Always give the input an accessible name (
aria-labelor a label). - Disabled items (
aria-disabled="true") are skipped by keyboard and ignored on click.
Theming tokens
Section titled “Theming tokens”| Token path | Purpose |
|---|---|
command.bg / fg / border / radius | Palette surface. |
command.input-* / placeholder-fg | Search row. |
command.list-max-height / list-padding-block | Scrollable list. |
command.heading-* | Group headings. |
command.item-* | Item rows, including the item-active-bg highlight (tracks data-color). |
command.shortcut-* | Shortcut chips. |
command.empty-* | Empty state. |
command.dialog-width / dialog-offset / backdrop | The .hc-command-dialog wrapper. |
CSS variables
Section titled “CSS variables”Show the generated CSS variables
--hc-command-bg | -fg | -border | -radius--hc-command-input-height | -input-padding-x | -input-font-size | -placeholder-fg--hc-command-list-padding-block | -list-max-height--hc-command-heading-fg | -heading-font-size | -heading-font-weight | -heading-padding-y--hc-command-item-padding-x | -item-padding-y | -item-gap | -item-font-size--hc-command-item-fg | -item-hover-bg | -item-active-bg | -item-active-fg | -item-disabled-fg--hc-command-shortcut-fg | -shortcut-font-size | -shortcut-padding-x--hc-command-empty-fg | -empty-padding-y--hc-command-dialog-width | -dialog-offset | -backdrop