Command — <mv-command>
Command palette: accent-insensitive fuzzy search (keywords included), ranked and grouped results, keyboard navigation with aria-activedescendant; standalone or inside a Dialog opened with ⌘K.
Category Overlays Type Web Component (<mv-command>) Status stable Keywords command, 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
Get Command. Included in every plan: the Pro pack ships all 226 components with the CLI and the MCP server used below.
See plans
Install
node scripts/add.mjs command --out ./src/marvelous
AI agent with the Marvelous UI MCP server: install_components({ slugs: ["command"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Files copied (dependencies included): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, components/command/command.js, components/command/command.css.
Usage
Canonical markup — start from it and customize with attributes, data-* and CSS variables:
<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
Name Type Default Description labelstring CommandsAccessible name of the list (and of the input if it has no label). manualboolean Disables built-in filtering (async results): listen to mv-filter and replace the items — they are picked up automatically.
Properties
Name Type Description querystring Search text (read/write; writing re-runs the filter). activeItemElement | null Currently active item.
Methods
Name Description clear()Clears the search. focus()Moves focus to the input.
Events
Name Description mv-selectEnter or click. detail: { value, label, item }. mv-filterAfter each filter pass. detail: { query, count }.
Content structure
Name Description <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
Name Description 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
Name Default Description --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.