Command <mv-command>

Paleta de comandos: busca aproximada que ignora acentos (palavras-chave incluídas), resultados classificados e agrupados, navegação por teclado com aria-activedescendant; independente ou dentro de um Dialog aberto com ⌘K.

CategoriaSobreposições
TipoWeb Component (<mv-command>)
Statusestável
Keywordscommand, command-palette, cmdk, search, combobox, spotlight, launcher

When to use

  • Power users need a Ctrl/Cmd+K palette to jump to pages or run actions by typing
  • A large set of actions or records must be searchable with fuzzy, grouped results and keyboard navigation
  • Results come from a server and the list must be replaced as the user types

Avoid when

  • The user is picking a value for a form field → use Combobox instead
  • Only a handful of actions are tied to a single trigger button → use Menu instead
  • Shoppers search a product catalog with recent and trending suggestions → use Search Autocomplete instead

Instalação

node scripts/add.mjs command --out ./src/marvelous

Agente de IA com o servidor MCP do Marvelous UI: install_components({ slugs: ["command"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Arquivos copiados (dependências incluídas): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, components/command/command.js, components/command/command.css.

Uso

Marcação de referência, para usar como ponto de partida e personalizar com atributos, data-* e variáveis CSS:

<div id="command-demo" style="display:grid;gap:1.25rem;justify-items:center;width:min(28rem,100%)">
  <mv-command label="Quick search">
    <input placeholder="Type a command or search…">
    <div class="mv-command-list">
      <div class="mv-command-group" data-label="Suggestions">
        <div class="mv-command-item" data-value="calendar" data-keywords="agenda schedule meetings">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
          Calendar
        </div>
        <div class="mv-command-item" data-value="emoji" data-keywords="smiley emoticon">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2M9 9h.01M15 9h.01"/></svg>
          Search emoji
        </div>
        <div class="mv-command-item" data-value="calculator" data-disabled>
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="2" width="16" height="20" rx="2"/><path d="M8 6h8M8 14h.01M12 14h.01M16 14h.01M8 18h.01M12 18h.01M16 18h.01"/></svg>
          Calculator
        </div>
      </div>
      <hr class="mv-command-separator">
      <div class="mv-command-group" data-label="Settings">
        <div class="mv-command-item" data-value="profile" data-keywords="account avatar">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg>
          Profile <span class="mv-command-shortcut"><kbd>⌘</kbd><kbd>P</kbd></span>
        </div>
        <div class="mv-command-item" data-value="billing" data-keywords="payment subscription card">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20"/></svg>
          Billing <span class="mv-command-shortcut"><kbd>⌘</kbd><kbd>B</kbd></span>
        </div>
        <div class="mv-command-item" data-value="settings" data-keywords="preferences options">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 21v-7M4 10V3M12 21v-9M12 8V3M20 21v-5M20 12V3M1 14h6M9 8h6M17 16h6"/></svg>
          Settings <span class="mv-command-shortcut"><kbd>⌘</kbd><kbd>,</kbd></span>
        </div>
      </div>
    </div>
    <div class="mv-command-empty">No results for this search.</div>
  </mv-command>

  <div style="display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;justify-content:center">
    <mv-dialog>
      <button class="mv-button" data-variant="outline" data-mv-open>
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
        Search…
        <span data-role="hotkey" style="margin-left:.5rem;padding:.125rem .375rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xs);background:var(--mv-bg-subtle);color:var(--mv-fg-muted);font-size:.6875rem">Ctrl K</span>
      </button>
      <dialog class="mv-command-dialog" aria-label="Command palette">
        <mv-command label="Command palette">
          <input placeholder="Search pages and actions…" autofocus>
          <div class="mv-command-list">
            <div class="mv-command-group" data-label="Navigation">
              <div class="mv-command-item" data-value="dashboard" data-keywords="home overview">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></svg>
                Dashboard <span class="mv-command-item-meta">Home</span>
              </div>
              <div class="mv-command-item" data-value="projects" data-keywords="folders work">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>
                Projects
              </div>
              <div class="mv-command-item" data-value="invoices" data-keywords="billing payments quotes">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 2h9l5 5v15H6z"/><path d="M14 2v6h6M9 13h6M9 17h4"/></svg>
                Invoices
              </div>
            </div>
            <div class="mv-command-group" data-label="Actions">
              <div class="mv-command-item" data-value="new-project" data-keywords="create add">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
                New project <span class="mv-command-shortcut"><kbd>⌘</kbd><kbd>N</kbd></span>
              </div>
              <div class="mv-command-item" data-value="invite" data-keywords="team member collaborator share">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="9" cy="8" r="4"/><path d="M2 21a7 7 0 0 1 14 0M19 8v6M16 11h6"/></svg>
                Invite a member
              </div>
              <div class="mv-command-item" data-value="theme" data-keywords="dark light mode appearance">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
                Toggle theme <span class="mv-command-shortcut"><kbd>⇧</kbd><kbd>⌘</kbd><kbd>L</kbd></span>
              </div>
            </div>
            <div class="mv-command-group" data-label="Help">
              <div class="mv-command-item" data-value="documentation" data-keywords="guide docs help">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/></svg>
                Documentation
              </div>
              <div class="mv-command-item" data-value="support" data-keywords="contact help assistance">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
                Contact support
              </div>
            </div>
          </div>
          <div class="mv-command-empty">No results. Try “project” or “theme”.</div>
          <div class="mv-command-footer" aria-hidden="true">
            <span><kbd><svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5M5 12l7-7 7 7"/></svg></kbd><kbd><svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M19 12l-7 7-7-7"/></svg></kbd> navigate</span>
            <span><kbd><svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 4v7a4 4 0 0 1-4 4H4M9 10l-5 5 5 5"/></svg></kbd> open</span>
            <span style="margin-left:auto"><kbd>Esc</kbd> close</span>
          </div>
        </mv-command>
      </dialog>
    </mv-dialog>
  </div>
  <p data-role="out" aria-live="polite" style="margin:0;font-size:.8125rem;color:var(--mv-fg-muted)">Nothing selected yet.</p>
</div>
<script type="module">
  const root = document.getElementById("command-demo");
  const out = root.querySelector('[data-role="out"]');
  const host = root.querySelector("mv-dialog");
  const dialog = host.querySelector("dialog");
  const palette = dialog.querySelector("mv-command");
  const mac = /Mac|iPhone|iPad/.test(navigator.platform);
  if (mac) root.querySelector('[data-role="hotkey"]').textContent = "⌘ K";

  root.addEventListener("mv-select", (e) => {
    out.textContent = `Selected: ${e.detail.label}`;
    if (dialog.contains(e.target)) host.close(e.detail.value);
  });
  dialog.addEventListener("close", () => palette.clear());

  // ⌘K / Ctrl+K toggles the palette (the listener removes itself when the demo is gone).
  document.addEventListener("keydown", function onKey(e) {
    if (!root.isConnected) return document.removeEventListener("keydown", onKey);
    if (e.key.toLowerCase() !== "k" || !(e.metaKey || e.ctrlKey) || e.altKey || e.shiftKey) return;
    e.preventDefault();
    dialog.open ? host.close() : host.show();
  });
</script>

API

Attributes

NameTipoDefaultDescription
labelstringCommandsAccessible name of the list (and of the input if it has no label).
manualbooleanDisables built-in filtering (async results): listen to mv-filter and replace the items. They are picked up automatically.

Properties

NameTipoDescription
querystringSearch text (read/write; writing re-runs the filter).
activeItemElement | nullCurrently active item.

Methods

NameDescription
clear()Clears the search.
focus()Moves focus to the input.

Events

NameDescription
mv-selectEnter or click. detail: { value, label, item }.
mv-filterAfter each filter pass. detail: { query, count }.

Content structure

NameDescription
<input>Search input (automatically wrapped with a magnifier icon).
.mv-command-listList (role=listbox).
.mv-command-group[data-label]Titled group; hidden when it has no matching results.
.mv-command-itemOption: data-value (→ detail.value), data-keywords (hidden searchable terms), data-disabled, data-always (always shown).
.mv-command-separatorSeparator, hidden while searching.
.mv-command-emptyEmpty state, announced (role=status).

CSS classes

NameDescription
mv-command-shortcutRight-aligned shortcut; its <kbd> elements render as keys.
mv-command-item-metaSubtle secondary text inside an item.
mv-command-footerFooter hint bar (↑↓ ↵ Esc keys).
mv-command-dialogOn an mv-dialog’s <dialog>: palette at the top of the screen, no inner padding.

CSS variables

NameDefaultDescription
--mv-command-height20remMax height of the list.
--mv-command-item-height2.25remMin height of an item.
--mv-command-highlightActive item background.
--mv-command-bgBackground.
--mv-command-radiusRadius.
--mv-command-dialog-width36remWidth inside a dialog.

Accessibility

APG “Combobox” pattern with an always-visible listbox popup: focus stays in the input (role=combobox, aria-controls, aria-activedescendant), role=option items with aria-selected / aria-disabled, role=group groups named by their title. ↑↓ (wrapping), Ctrl+N/P or Ctrl+J/K, Page Up/Down, Ctrl+Home/End, Enter confirms; Home/End stay in the text. Esc first clears the search, then closes the dialog. The empty state is announced politely.

Esta página foi traduzida com IA. Informar um problema de tradução