Command
A command menu: a search field over a list of items, with groups, filters and keyboard control. Stand-alone or in a dialog as a palette.
A command menu with groups, a disabled item and shortcuts
A div.command with a header holding the
search field, and a role="menu" list of
role="group"s of
role="menuitem"s. Type to filter; the arrow
keys move the highlight and Enter chooses.
data-keywords adds words the search matches.
<div class="command" id="cmd-menu" aria-label="Command menu">
<header><i class="icon icon-search" aria-hidden="true"></i><input type="text" id="cmd-menu-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="cmd-menu-menu"></header>
<div role="menu" id="cmd-menu-menu" aria-orientation="vertical" data-empty="No results found.">
<div role="group" aria-labelledby="cmd-menu-suggestions">
<span role="heading" id="cmd-menu-suggestions">Suggestions</span>
<div role="menuitem" data-label="Calendar" data-keywords="date event schedule"><i class="icon icon-calendar" aria-hidden="true"></i><span>Calendar</span></div>
<div role="menuitem" data-label="Search emoji" data-keywords="emoji smile reaction"><i class="icon icon-smile" aria-hidden="true"></i><span>Search emoji</span></div>
<div role="menuitem" aria-disabled="true"><i class="icon icon-calculator" aria-hidden="true"></i><span>Calculator</span></div>
</div>
<hr role="separator">
<div role="group" aria-labelledby="cmd-menu-settings">
<span role="heading" id="cmd-menu-settings">Settings</span>
<div role="menuitem" data-label="Profile" data-keywords="user account"><i class="icon icon-user" aria-hidden="true"></i><span>Profile</span><span data-shortcut>⌘P</span></div>
<div role="menuitem" data-label="Billing" data-keywords="invoice payment"><i class="icon icon-credit-card" aria-hidden="true"></i><span>Billing</span><span data-shortcut>⌘B</span></div>
<div role="menuitem" data-label="Settings" data-keywords="config preferences"><i class="icon icon-settings" aria-hidden="true"></i><span>Settings</span><span data-shortcut>⌘S</span></div>
</div>
</div>
</div>
In a dialog, as a command palette
Put the command in a
<dialog class="command-dialog">. A
button with commandfor and
command="show-modal" opens it with the
field focused; choosing an item closes it, and so do Esc and a
click on the backdrop (closedby="any").
<button type="button" class="btn" commandfor="cmd-palette" command="show-modal">Open</button>
<dialog id="cmd-palette" class="command-dialog" aria-label="Command menu" closedby="any">
<div class="command">
<header><i class="icon icon-search" aria-hidden="true"></i><input type="text" id="cmd-palette-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="cmd-palette-menu"></header>
<div role="menu" id="cmd-palette-menu" aria-orientation="vertical" data-empty="No results found.">
<div role="group" aria-labelledby="cmd-palette-suggestions">
<span role="heading" id="cmd-palette-suggestions">Suggestions</span>
<div role="menuitem" data-label="Calendar" data-keywords="date event schedule"><i class="icon icon-calendar" aria-hidden="true"></i><span>Calendar</span></div>
<div role="menuitem" data-label="Search emoji" data-keywords="emoji smile reaction"><i class="icon icon-smile" aria-hidden="true"></i><span>Search emoji</span></div>
<div role="menuitem" aria-disabled="true"><i class="icon icon-calculator" aria-hidden="true"></i><span>Calculator</span></div>
</div>
<hr role="separator">
<div role="group" aria-labelledby="cmd-palette-settings">
<span role="heading" id="cmd-palette-settings">Settings</span>
<div role="menuitem" data-label="Profile" data-keywords="user account"><i class="icon icon-user" aria-hidden="true"></i><span>Profile</span><span data-shortcut>⌘P</span></div>
<div role="menuitem" data-label="Billing" data-keywords="invoice payment"><i class="icon icon-credit-card" aria-hidden="true"></i><span>Billing</span><span data-shortcut>⌘B</span></div>
<div role="menuitem" data-label="Settings" data-keywords="config preferences"><i class="icon icon-settings" aria-hidden="true"></i><span>Settings</span><span data-shortcut>⌘S</span></div>
</div>
</div>
</div>
</dialog>
Searching records, with filters
Groups with data-results show only once the field has
text. The filter buttons under the header count what they would
show; / lists the filters, and Backspace in the empty
field removes the chosen one. data-limit caps the
records and the data-more item shows more.
<div class="command" id="cmd-records" aria-label="Search">
<header><i class="icon icon-search" aria-hidden="true"></i><span class="badge" data-variant="outline" data-filter-badge hidden><span data-name></span><button type="button" aria-label="Remove filter"><i class="icon icon-x" aria-hidden="true"></i></button></span><input type="text" id="cmd-records-input" placeholder="Search orders and customers, or type / for filters..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="cmd-records-menu"></header>
<div data-filters aria-label="Filters">
<button type="button" class="btn" data-filter="orders" data-suggested><i class="icon icon-shopping-bag" aria-hidden="true"></i><span data-name>Orders</span><span data-count></span></button>
<button type="button" class="btn" data-filter="customers" data-suggested><i class="icon icon-user" aria-hidden="true"></i><span data-name>Customers</span><span data-count></span></button>
</div>
<div role="menu" id="cmd-records-menu" aria-orientation="vertical" data-empty="No results found." data-limit="3">
<div role="group" data-results data-filter="orders" aria-labelledby="cmd-records-orders">
<span role="heading" id="cmd-records-orders">Orders</span>
<a role="menuitem" href="#" data-label="#1042" data-keywords="Emma Wilson"><i class="icon icon-shopping-bag" aria-hidden="true"></i><span>#1042</span><span data-description>Emma Wilson, $72.18</span><span class="badge" data-variant="warning">Open</span></a>
<a role="menuitem" href="#" data-label="#1041" data-keywords="Emma Wilson"><i class="icon icon-shopping-bag" aria-hidden="true"></i><span>#1041</span><span data-description>Emma Wilson, $344.97</span><span class="badge" data-variant="info">Paid</span></a>
<a role="menuitem" href="#" data-label="#1040" data-keywords="Liam Chen"><i class="icon icon-shopping-bag" aria-hidden="true"></i><span>#1040</span><span data-description>Liam Chen, $59.99</span><span class="badge" data-variant="info">Paid</span></a>
<a role="menuitem" href="#" data-label="#1039" data-keywords="Noah Davis"><i class="icon icon-shopping-bag" aria-hidden="true"></i><span>#1039</span><span data-description>Noah Davis, $128.50</span><span class="badge" data-variant="ok">Shipped</span></a>
<a role="menuitem" href="#" data-label="#1038" data-keywords="Olivia Brown"><i class="icon icon-shopping-bag" aria-hidden="true"></i><span>#1038</span><span data-description>Olivia Brown, $19.00</span><span class="badge" data-variant="info">Paid</span></a>
</div>
<div role="group" data-results data-filter="customers" aria-labelledby="cmd-records-customers">
<span role="heading" id="cmd-records-customers">Customers</span>
<a role="menuitem" href="#" data-label="Emma Wilson" data-keywords="emma.wilson@example.com"><i class="icon icon-user" aria-hidden="true"></i><span>Emma Wilson</span><span data-description>emma.wilson@example.com</span></a>
<a role="menuitem" href="#" data-label="Liam Chen" data-keywords="liam.chen@example.com"><i class="icon icon-user" aria-hidden="true"></i><span>Liam Chen</span><span data-description>liam.chen@example.com</span></a>
<a role="menuitem" href="#" data-label="Noah Davis" data-keywords="noah.davis@example.com"><i class="icon icon-user" aria-hidden="true"></i><span>Noah Davis</span><span data-description>noah.davis@example.com</span></a>
</div>
<div role="group" data-filter-list aria-labelledby="cmd-records-filters">
<span role="heading" id="cmd-records-filters">Filters</span>
<div role="menuitem" data-filter="orders" data-label="Orders"><i class="icon icon-shopping-bag" aria-hidden="true"></i><span>Orders</span></div>
<div role="menuitem" data-filter="customers" data-label="Customers"><i class="icon icon-user" aria-hidden="true"></i><span>Customers</span></div>
</div>
<div role="group" aria-labelledby="cmd-records-pages">
<span role="heading" id="cmd-records-pages">Pages</span>
<a role="menuitem" href="#" data-label="Orders"><i class="icon icon-shopping-bag" aria-hidden="true"></i><span>Orders</span></a>
<a role="menuitem" href="#" data-label="Customers"><i class="icon icon-user" aria-hidden="true"></i><span>Customers</span></a>
</div>
<div role="menuitem" data-more="20"><i class="icon icon-list-filter" aria-hidden="true"></i><span>Show <span data-count></span> more results</span></div>
</div>
</div>
Tab filters, key hints and a group still loading
data-hint shows an item's keys while it is active.
Tab on an item with data-tab-filter chooses that
filter. A results group with
aria-busy="true" shows none of its records
and the menu says Searching...; remove the attribute when the
records arrive. Type emma to see it.
<div class="command" id="cmd-tab" aria-label="Search">
<header><i class="icon icon-search" aria-hidden="true"></i><span class="badge" data-variant="outline" data-filter-badge hidden><span data-name></span><button type="button" aria-label="Remove filter"><i class="icon icon-x" aria-hidden="true"></i></button></span><input type="text" id="cmd-tab-input" placeholder="Search, or press Tab on a page to filter by it..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="cmd-tab-menu"></header>
<div data-filters aria-label="Filters">
<button type="button" class="btn" data-filter="orders" data-suggested><i class="icon icon-shopping-bag" aria-hidden="true"></i><span data-name>Orders</span><span data-count></span></button>
<button type="button" class="btn" data-filter="customers" data-suggested><i class="icon icon-user" aria-hidden="true"></i><span data-name>Customers</span><span data-count></span></button>
</div>
<div role="menu" id="cmd-tab-menu" aria-orientation="vertical" data-empty="No results found.">
<div role="group" data-results data-filter="orders" aria-labelledby="cmd-tab-orders">
<span role="heading" id="cmd-tab-orders">Orders</span>
<a role="menuitem" href="#" data-label="#1042" data-keywords="Emma Wilson"><i class="icon icon-shopping-bag" aria-hidden="true"></i><span>#1042</span><span data-description>Emma Wilson, $72.18</span><span data-hint><kbd class="kbd">Enter</kbd> open</span></a>
<a role="menuitem" href="#" data-label="#1041" data-keywords="Emma Wilson"><i class="icon icon-shopping-bag" aria-hidden="true"></i><span>#1041</span><span data-description>Emma Wilson, $344.97</span><span data-hint><kbd class="kbd">Enter</kbd> open</span></a>
</div>
<div role="group" data-results data-filter="customers" aria-busy="true" aria-labelledby="cmd-tab-customers">
<span role="heading" id="cmd-tab-customers">Customers</span>
<a role="menuitem" href="#" data-label="Emma Wilson" data-keywords="emma.wilson@example.com"><i class="icon icon-user" aria-hidden="true"></i><span>Emma Wilson</span><span data-description>emma.wilson@example.com</span></a>
</div>
<div role="group" aria-labelledby="cmd-tab-pages">
<span role="heading" id="cmd-tab-pages">Pages</span>
<a role="menuitem" href="#" data-label="Orders" data-tab-filter="orders"><i class="icon icon-shopping-bag" aria-hidden="true"></i><span>Orders</span><span data-hint><kbd class="kbd">Tab</kbd> search orders</span></a>
<a role="menuitem" href="#" data-label="Customers" data-tab-filter="customers"><i class="icon icon-user" aria-hidden="true"></i><span>Customers</span><span data-hint><kbd class="kbd">Tab</kbd> search customers</span></a>
</div>
</div>
</div>
Open it with a key
data-hotkey on the
dialog.command-dialog opens it from the keyboard, and
the same key closes it. Write the keys joined by +:
mod is Ctrl or Cmd, then ctrl,
meta, alt and shift, and
last the key, such as mod+k,
ctrl+shift+p or /. Separate several keys
with spaces. A key with no modifier is ignored while you type in a
field. Try Alt K here.
<button type="button" class="btn" commandfor="cmd-hotkey" command="show-modal">Open <kbd class="kbd">Alt K</kbd></button>
<dialog id="cmd-hotkey" class="command-dialog" aria-label="Command menu" closedby="any" data-hotkey="alt+k">
<div class="command">
<header>
<i class="icon icon-search" aria-hidden="true"></i>
<input type="text" id="cmd-hotkey-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="cmd-hotkey-menu">
</header>
<div role="menu" id="cmd-hotkey-menu" aria-orientation="vertical" data-empty="No results found.">
<div role="group" aria-labelledby="cmd-hotkey-go">
<span role="heading" id="cmd-hotkey-go">Go to</span>
<a role="menuitem" href="#" data-label="Orders"><i class="icon icon-shopping-bag" aria-hidden="true"></i><span>Orders</span></a>
<a role="menuitem" href="#" data-label="Customers"><i class="icon icon-user" aria-hidden="true"></i><span>Customers</span></a>
</div>
</div>
</div>
</dialog>
API
The command has no methods and sends no events of its own. Each
item is a link or a button: listen for its click.
Inside a dialog.command-dialog, choosing an item
closes the dialog, except a filter item and Show more. The script
starts every .command on the page, also one added
later.
Keyboard
| Key | Description |
|---|---|
| ↓ ↑ | Move to the next or the previous item. A disabled or hidden item is skipped. |
| Enter | Chooses the active item, as a click on it does. |
| / |
Typed first in the field: lists the filters (the
data-filter-list group).
|
| Backspace | In an empty field: removes the chosen filter. |
| Tab |
On an item with data-tab-filter: chooses that
filter and empties the field.
|
| Hotkey |
The data-hotkey of a command dialog: opens
it, or closes it when it is open.
|
| Esc |
In a dialog.command-dialog: closes the
dialog. This is the dialog's own behaviour.
|
Attributes
| Attribute / element | Values | Description |
|---|---|---|
class="command" |
on a <div> |
The component: a <header> with the
search <input>, an optional
data-filters row and a
role="menu" list.
|
class="command-dialog" |
on a <dialog> |
A palette: the dialog holds one .command.
Choosing an item closes it, except a filter item and Show
more.
|
role="menuitem" |
on an <a>,
<button> or <div>
|
An item. Listen for its click; Enter on the
active item clicks it.
|
role="group" |
with a role="heading" span |
A group of items. It is hidden while none of its items show. |
role="separator" |
an <hr> |
A line between groups. Hidden while the field has text or a filter is chosen. |
data-label |
text on an item | The name the search matches, for an item whose content is more than text. Default: the item's text. |
data-keywords |
words on an item | Extra words the search matches without showing them. |
aria-disabled="true" |
on an item | The item is skipped by the keyboard and cannot be chosen. |
aria-hidden |
true, false |
Set by the script on an item the search has filtered out. |
data-empty |
text on the menu | The message when nothing matches. Default: No results found. |
data-description |
on a span in an item | Muted text after the item's name, cut with an ellipsis when long. |
data-shortcut |
on a span in an item | A shortcut at the end of the item, such as ⌘P. |
data-hint |
on a span in an item |
The item's keys, as <kbd> elements and
what each does. It shows while the item is active.
|
data-results |
on a group | The group holds records. It shows while the field has text, or while its filter is chosen. |
data-filter |
a key, such as orders; on a results group, a
filter button or a filter item
|
Names the filter. Choosing it shows only the results
groups with the same key. The command carries the chosen
key as its own data-filter.
|
data-filters |
on a div after the header |
The row of filter buttons. Each button's
[data-count] is filled with the number of
records it would show.
|
data-suggested |
on a filter button | The button shows while the field is empty. Without it, a button shows only while it has a match. |
data-filter-list |
on a group |
The filters listed when the field starts with
/. Choosing one chooses its
data-filter and empties the field.
|
data-filter-badge |
on a badge in the header |
Names the chosen filter in its [data-name].
Its button removes the filter.
|
data-limit |
a number on the menu | The most records shown at once. Default: no limit. |
data-more |
a number on an item |
Show more: choosing it shows that many more records. Its
[data-count] says how many.
|
aria-busy="true" |
on a results group |
The group's records are on their way and none show. The
menu is then aria-busy too, set by the
script, and says Searching... in place of the empty
message.
|
data-tab-filter |
a filter's key on an item | Tab on the active item chooses that filter and empties the field. |
data-hotkey |
keys such as mod+k, on a
dialog.command-dialog
|
Opens the dialog from the keyboard; the same keys close
it. mod is Ctrl or Cmd. Several keys are
separated by spaces. Default: none.
|
data-command-initialized |
true, set by the script |
Marks a command the script has started. Before it, items still show a hover. |