Command

ui-command is a search box above a filtered list of options. The options come from plain <option> children, and native <optgroup label="…"> elements group them under a heading. Type to filter by label or value, use the arrow keys to move the highlight and press Enter, or click an option, to fire a select event. data-shortcut on an option shows a shortcut hint, and ui-command-empty sets what shows when nothing matches.

ui-command fills the width of its container. On its own it renders inline. Put it in a Dialog to make a command palette. The search on this site works that way.

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Command demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
  <style>
    .demo-narrow {
      width: min(100%, calc(var(--spacing) * 88));
    }
  </style>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/command.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/toaster.html"></import-element>
  <ui-toaster></ui-toaster>

  <div class="demo-narrow">
    <ui-command onselect="document.dispatchEvent(new CustomEvent('toast', { detail: { title: event.detail.label } }))">
      <optgroup label="Suggestions">
        <option value="calendar">Calendar</option>
        <option value="emoji">Search emoji</option>
        <option value="calculator">Calculator</option>
      </optgroup>
      <optgroup label="Settings">
        <option value="profile" data-shortcut="⌘P">Profile</option>
        <option value="billing" data-shortcut="⌘B">Billing</option>
        <option value="settings" data-shortcut="⌘S">Settings</option>
      </optgroup>
    </ui-command>
  </div>
</body>
</html>

Examples

Command palette in a dialog

Put ui-command in a ui-dialog with hide-close, open the dialog with showModal() and close it from the select handler. Focus the search input after opening so the user can type straight away.

Previewindex.htmlcommand-palette-demo.html
Open command palette
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Command demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/command.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/dialog.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/toaster.html"></import-element>
  <ui-toaster></ui-toaster>

  <import-element from="./command-palette-demo.html"></import-element>
  <command-palette-demo></command-palette-demo>
</body>
</html>
<custom-element name="command-palette-demo">
  <script>
    function open() {
      refs.palette.showModal();
      refs.command.shadowRoot.querySelector("input").focus();
    }

    function run(event) {
      refs.palette.close();
      document.dispatchEvent(new CustomEvent("toast", { detail: { title: `Running "${event.detail.label}"` } }));
    }
  </script>

  <style>
    ui-command {
      margin: calc(var(--spacing) * -6);
      border: 0;
    }
  </style>

  <ui-button variant="outline" onclick="open()">Open command palette</ui-button>
  <ui-dialog ref="palette" label="Command palette" hide-close>
    <ui-command ref="command" placeholder="Type a command..." onselect="run(event)">
      <optgroup label="File">
        <option value="new-file" data-shortcut="⌘N">New file</option>
        <option value="open-file" data-shortcut="⌘O">Open file</option>
      </optgroup>
      <optgroup label="View">
        <option value="toggle-theme">Toggle theme</option>
        <option value="zoom-in" data-shortcut="⌘+">Zoom in</option>
      </optgroup>
    </ui-command>
  </ui-dialog>
</custom-element>

Options from state

Generate the groups and options with repeat. ui-command watches its children, so the list updates when the state changes. value falls back to the option text, and the label attribute overrides the text.

Previewindex.htmlcommand-state-demo.html
Nobody matches.
Picked: Add Margaret
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Command demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/command.html"></import-element>

  <import-element from="./command-state-demo.html"></import-element>
  <command-state-demo></command-state-demo>
</body>
</html>
<custom-element name="command-state-demo">
  <script>
    const people = useState({
      teams: [
        {
          name: "Engineering",
          members: [
            { id: "ada", name: "Ada Lovelace" },
            { id: "grace", name: "Grace Hopper" },
          ],
        },
        { name: "Research", members: [{ id: "katherine", name: "Katherine Johnson" }] },
      ],
      picked: "nobody",
    });

    function pick(event) {
      people.picked = event.detail.label;
    }

    function add() {
      people.teams[1].members.push({ id: "margaret", name: "Margaret Hamilton" });
    }
  </script>

  <style>
    :host {
      display: grid;
      gap: calc(var(--spacing) * 3);
      width: min(100%, calc(var(--spacing) * 112));
      font-size: var(--text-sm);
    }
    .row {
      display: flex;
      align-items: center;
      justify-content: space-between;
    }
  </style>

  <ui-command placeholder="Find a person..." onselect="pick(event)">
    <optgroup repeat="#people.teams" as="team" key="name" _label="#team.name">
      <option repeat="#team.members" as="person" key="id" _value="#person.id" _label="#person.name"></option>
    </optgroup>
    <ui-command-empty>Nobody matches.</ui-command-empty>
  </ui-command>
  <div class="row">
    <span>Picked: <slot name="#people.picked"></slot></span>
    <ui-button size="sm" variant="secondary" onclick="add()">Add Margaret</ui-button>
  </div>
</custom-element>

Empty state

ui-command-empty replaces the default "No results found." text, and can hold markup. Type something that is not in the list to see it.

Previewindex.html
No framework found. Suggest one
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Command demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
  <style>
    .demo-narrow {
      width: min(100%, calc(var(--spacing) * 88));
    }
  </style>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/command.html"></import-element>

  <div class="demo-narrow">
    <ui-command placeholder="Search frameworks...">
      <option value="realm">REALM</option>
      <option value="lit">Lit</option>
      <option value="stencil">Stencil</option>
      <ui-command-empty>No framework found. <a href="#command">Suggest one</a></ui-command-empty>
    </ui-command>
  </div>
</body>
</html>

API

Parts

Part Slot Renders
ui-command A bordered column with the search input above the filtered list. Fills its container's width.
<optgroup label="…"> (data) A group with the label as its heading. Groups after the first are separated by a line.
<option> (data) One row in the list, with the data-shortcut hint at the end.
ui-command-empty empty The text shown when nothing matches, in place of "No results found.".

<option> and <optgroup> are hidden and used only as data. Changing them updates the list. Options outside a group are listed without a heading.

ui-command attributes

Attribute Type Default Description
placeholder string "Type a command or search..." Placeholder of the search input.

Option attributes

Attribute Description
value Sent in event.detail.value. Also matched by the search. Defaults to the option text.
label Text shown in the list and matched by the search. Defaults to the option text.
data-shortcut Shortcut hint shown at the end of the row. Display only. It does not bind a key.

Events

Event Detail Description
select { value, label } Fired when an option is clicked or Enter is pressed on the highlighted option. Bubbles and is composed.

CSS parts

Part Description
search The row with the search icon and input.
input The search <input>.
list The scrolling list (role="listbox").
item Each option row (role="option").
shortcut The shortcut hint in a row.
empty The paragraph shown when nothing matches.

Accessibility

Edit this page on GitHub · View as Markdown