Basic Dropdown
A menu button with the full keyboard model - arrows, Home/End, Escape, focus restore.
6 frameworksIntermediate
A search-driven command list steered by a virtual highlight, combobox-style.
<!--
A command palette keeps focus in the input and moves a virtual highlight through
the list with aria-activedescendant - the combobox pattern, not roving focus,
because you must keep typing while you steer. The input is role="combobox"
pointing at a role="listbox"; Enter runs the highlighted option. Capped at
calc(100vw-2rem) so it never leaves a phone.
-->
<div class="relative inline-block">
<button type="button" aria-haspopup="dialog" aria-expanded="true" class="inline-flex items-center gap-1.5 rounded-lg border border-gray-300 bg-white px-3.5 py-2 text-sm font-semibold text-gray-700 hover:bg-gray-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-900">
Command
<kbd class="rounded border border-gray-300 px-1 text-[10px] font-medium text-gray-500 dark:border-gray-600 dark:text-gray-400">⌘K</kbd>
</button>
<div class="absolute left-0 top-[calc(100%+0.375rem)] z-20 w-72 max-w-[calc(100vw-2rem)] overflow-hidden rounded-xl border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-900">
<div class="border-b border-gray-200 p-2 dark:border-gray-700">
<input type="text" role="combobox" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-opt-new-doc" aria-autocomplete="list" placeholder="Type a command…" aria-label="Command" class="w-full rounded-md bg-transparent px-1.5 py-1 text-sm text-gray-900 placeholder:text-gray-400 focus-visible:outline-none dark:text-gray-100 dark:placeholder:text-gray-500" />
</div>
<ul id="cmd-list" role="listbox" aria-label="Command" class="max-h-56 overflow-y-auto p-1">
<li id="cmd-opt-new-doc" role="option" aria-selected="true" class="flex cursor-pointer items-center justify-between gap-2 rounded-md bg-gray-100 px-3 py-2 text-sm text-gray-900 dark:bg-gray-800 dark:text-gray-100">
<span class="truncate">New document</span>
<kbd class="rounded border border-gray-300 px-1 text-[10px] font-medium text-gray-500 dark:border-gray-600 dark:text-gray-400">C</kbd>
</li>
<li id="cmd-opt-search" role="option" aria-selected="false" class="flex cursor-pointer items-center justify-between gap-2 rounded-md px-3 py-2 text-sm text-gray-700 dark:text-gray-300">
<span class="truncate">Search everything</span>
<kbd class="rounded border border-gray-300 px-1 text-[10px] font-medium text-gray-500 dark:border-gray-600 dark:text-gray-400">/</kbd>
</li>
<li id="cmd-opt-invite" role="option" aria-selected="false" class="flex cursor-pointer items-center justify-between gap-2 rounded-md px-3 py-2 text-sm text-gray-700 dark:text-gray-300">
<span class="truncate">Invite teammate</span>
</li>
</ul>
</div>
</div>| Prop | Type | Default | Description |
|---|---|---|---|
labelrequired | string | - | Accessible label announced while loading. |
commandsrequired | Command[] | - | The array of items to render. |
placeholder | string | - | Placeholder |
onRun | (id: string) => void | - | On run |
A command palette keeps focus in the input and moves a virtual highlight through the list with `aria-activedescendant` - the combobox pattern, not roving focus, because you have to keep typing while you steer. The input is a `role="combobox"` pointing at a `role="listbox"`; ArrowUp/Down wrap the highlight and Enter runs it. Filtering resets the highlight to the top so Enter never fires a stale command. The panel is width-capped and scrolls internally, so it survives a 320px screen.