CommandPalette
CommandPalette é um índice buscável de comandos e destinos. Escolha entre abrir como modal global ou
incorporar com inline; use Dropdown quando uma lista curta de comandos pertencer a um único controle.
Exemplos
Lista de comandos inline
inline renderiza apenas o painel, então funciona em documentação e superfícies incorporadas sem
overlay, portal, focus trap ou scroll lock. hint é texto secundário e também participa do
filtro; shortcut apenas exibe teclas separadas por espaços.
Dicas de teclado parciais
hints se mistura aos padrões, então mudar navigate e select preserva a dica de fechar. O
emptyMessage padrão é seguido pela busca, então use uma abertura de frase como “No results for”.
Trigger
Abrindo por um gatilho
CommandPalette.Trigger é uma propriedade estática do mesmo import. Passe o atalho que deseja
exibir: ele nunca detecta Command versus Ctrl durante o render, pois isso causaria hydration
mismatch.
Return focus
In modal mode, use returnFocusTo for pointer-opened palettes. It is a synchronous resolver that
runs once after an accepted close has committed, so it reads the current trigger and workflow state
rather than a stale element. Keyboard opening can omit it when the captured opener remains a valid
destination.
import { CommandPalette, type CommandGroup } from '@lyra-ds/react';
import { useRef, useState } from 'react';
function CommandPaletteExample({ canReturnToTrigger }: { canReturnToTrigger: boolean }) {
const [open, setOpen] = useState(false);
const [showTrigger, setShowTrigger] = useState(true);
const triggerRef = useRef<HTMLButtonElement>(null);
const successorRef = useRef<HTMLHeadingElement>(null);
const groups: CommandGroup[] = [{ items: [{ id: 'projects', label: 'Projects' }] }];
return (
<>
{showTrigger && (
<CommandPalette.Trigger
ref={triggerRef}
label="Search commands"
onClick={() => setOpen(true)}
/>
)}
<h2 ref={successorRef} tabIndex={-1}>
Projects
</h2>
<CommandPalette
open={open}
onClose={() => {
setShowTrigger(false);
setOpen(false);
}}
returnFocusTo={() =>
canReturnToTrigger && triggerRef.current ? triggerRef.current : successorRef.current
}
groups={groups}
/>
</>
);
}The returned element must be connected, visible, enabled, in the same document, and programmatically
focusable. A named region with tabIndex={-1} is a valid successor when the trigger is removed or
no longer meaningful; a removed trigger's null ref falls back to that successor even if availability
has not changed. Hidden, inert, and aria-hidden ancestors make a target ineligible, as do the
closing panel, overlay, and their descendants while exit presence keeps them mounted. Invalid results
fall back to a valid captured opener. If neither target is eligible, Lyra makes no invalid focus call
and emits a development diagnostic once for that close cycle. After removal the browser may leave
focus on body. That is an invalid invoking composition, not a
successful restoration. returnFocusTo is ignored by inline, and it never runs for ignored close
requests or closed renders. Omitted pointer-opened consumers are not automatically repaired.
Initial focus
initialFocusTo is optional and has the type () => HTMLElement | null. In modal mode, Lyra calls
this synchronous, read-only resolver once for each accepted opening. Normal command interaction omits
it, so the eligible search field remains the task-control default. When an application intentionally
needs a declared destination, it can return the current named palette panel, which is already present
with tabIndex={-1}. A null or invalid result focuses that panel directly; it never falls through to
another control. Inline palettes ignore initialFocusTo.
An eligible target is an HTMLElement in the panel's document and current modal panel, connected,
visible with rendered rectangles, enabled (including its fieldset), programmatically focusable, and
outside hidden, inert, or aria-hidden ancestry. The resolver is not replayed on rerenders; a later
accepted opening reads the current resolver. Form composition owns focus after failed validation, so
initial entry never infers failure from aria-invalid.
The resolver only reads committed state and refs: do not focus, mutate, or start asynchronous work. If it throws, Lyra focuses the panel and propagates the same error; the palette's animation-frame context reports it through normal browser error handling, not a React error boundary.
Quando usar
Use CommandPalette quando as pessoas precisarem de um índice buscável de ações ou destinos, acessível pelo teclado.
Prefira outro componente quando:
- Poucos comandos pertencem a um controle visível — use Dropdown.
- A escolha muda o workspace ativo — use WorkspaceSwitcher, que informa a seleção.
- A pessoa alterna entre poucas visualizações pares — use Tabs.
Acessibilidade
- In inline and modal mode, the actual search input and command items have a 44px minimum target
in both dimensions for fine, coarse and hybrid pointers, without brand configuration. Their
minimum height uses
max(44px, var(--control-md)); set the existing--control-mdsizing token on the palette or an ancestor (for example,--control-md: 56px) to raise it. Smaller values keep the 44px floor. - O campo de busca é um
role="combobox"comaria-expanded,aria-controls,aria-autocomplete="list"earia-activedescendant. Os resultados sãorole="option"dentro de umrole="listbox"; grupos rotulados usamrole="group". - Durante a busca normal, o foco DOM fica no campo. As setas movem o foco virtual pelas opções,
permitindo continuar digitando enquanto o descendente ativo muda; Enter seleciona o item ativo e
Escape chama
onClose. Um destino declarado no painel da paleta também é suportado. - O
onSelectde um item roda antes do callback do palette. No modo modal, passeonClosepara atualizaropen; usereturnFocusToquando uma paleta aberta por ponteiro precisar de um destino explícito. - O modo modal faz portal de um
role="dialog"nomeado, prende o foco, bloqueia a rolagem e permanece montado durante a animação de saída. Passearia-labelpara substituir “Command palette” numa interface localizada. O modo inline não é diálogo e não tem nome de diálogo. - O campo de busca de comandos tem o nome "Search commands" por padrão. Passe
searchLabelpara traduzi-lo. onOpeninstala o listener global Command/Ctrl+hotkey(kpor padrão). Ele fica de propósito fora destes exemplos porque este site de documentação já é dono desse atalho.- Abaixo de 720px, Trigger se reduz ao ícone enquanto o rótulo visível e o atalho ficam ocultos. Ele
preserva o nome acessível de
label; o ícone de busca é decorativo, então esse rótulo deve ser significativo.
API e código
| Nome | Tipo | Obrigatória | Descrição |
|---|---|---|---|
open | boolean | — | Controls modal visibility. On close, the overlay and panel remain mounted for their exit motion. Ignored in inline mode. |
onClose | () => void | — | Called when the palette is dismissed or an item is selected. |
onOpen | () => void | — | Enables the global Command/Ctrl+hotkey listener and is called to open the palette. |
onSelect | (item: CommandItem) => void | — | Called with the chosen item after its own `onSelect` callback. |
groups | CommandGroup[] | — | Command groups to filter and render. |
placeholder | string | — | Search field placeholder. Default: `"Type a command or search…"`. |
emptyMessage | string | — | Text shown before the current query when no commands match. Default: `"No results for"`. |
searchLabel | string | — | Accessible name for the command search field. Default: `"Search commands"`. |
hints | CommandPaletteHints | — | Overrides for the footer keyboard hints. Merged over the defaults, so partial objects work. |
hotkey | string | — | Key used with Command/Ctrl for the global shortcut. Default: `"k"`. |
inline | boolean | — | Renders the panel without an overlay, portal, focus trap, or scroll lock. |
returnFocusTo | () => HTMLElement | null | — | Returns the current element to focus after an accepted modal close. Ignored in inline mode. |
initialFocusTo | () => HTMLElement | null | — | Resolves the initial focus destination inside the modal on each accepted opening. Ignored in inline mode. |
className | string | — | Additional class name appended to `.lyra-cmdk`. |
'aria-label' | string | — | Accessible name for the modal dialog. Default: `"Command palette"`. Translate it in a localized interface — it is what a screen reader announces when the palette opens. Ignored in inline mode, which is not a dialog. |
x-data="lyraCommandPalette({ … })"
| Opção | Tipo | Obrigatória | Descrição |
|---|---|---|---|
groups | LyraCommandPaletteGroup[] | — | Command groups rendered through nested consumer `x-for` templates. Default: `[]`. |
open | boolean | — | Overlay visibility; modelable with `x-modelable="open"`. Ignored in inline mode. |
placeholder | string | — | Search input placeholder. Default: `"Type a command or search…"`. |
emptyMessage | string | — | Text shown before a quoted unmatched query. Default: `"No results for"`. |
searchLabel | string | — | Accessible search-input name. Default: `"Search commands"`. |
hints | LyraCommandPaletteHints | — | Partial override for footer hint labels. |
hotkey | string | false | — | Command/Ctrl key used to toggle the overlay. Falsy disables it. Default: `"k"`. |
inline | boolean | — | Render only the panel, without modal behavior. Default: `false`. |
label | string | — | Accessible dialog name in overlay mode. Default: `"Command palette"`. |
returnFocusTo | () => HTMLElement | null | — | Resolves the current logical focus destination after an accepted modal close. |
After the existing Alpine plugin installation (Alpine.plugin(lyra)), use the registered
lyraCommandPalette binding; do not import its internal factory. This modal composition is
consumer-served and is not portaled automatically. The plugin owns its focus trap, scroll lock,
Escape, search, and active-descendant behavior.
returnFocusTo is initial binding configuration, not mutable callback data. On an accepted modal
close it synchronously reads current state or refs once: Lyra prefers its eligible explicit target
with focus({ preventScroll: true }), then an eligible captured opener with ordinary focus()
and scroll behavior. The target must be connected, visible, enabled, focusable, in the same document, outside
the closing overlay and panel, and outside hidden, inert, or aria-hidden content. There is no
arbitrary body fallback. Return a meaningful successor when the trigger is removed or becomes
unsafe; do not focus, mutate, or start asynchronous work from the resolver. This example treats an
absent, disabled, or hidden trigger as unavailable and returns the named successor. Other
application availability changes, including hidden or inert ancestry and a no-longer-meaningful
trigger, must choose their meaningful successor from current state; this example covers its named
simple states, not arbitrary automatic discovery. Initial closed state, opening, ignored close
requests, exit, and destroy do not resolve it. Inline mode ignores returnFocusTo.
<div
x-data="lyraCommandPalette({
groups: [],
hotkey: false,
label: 'Search commands',
searchLabel: 'Search commands',
returnFocusTo: () => {
const trigger = document.getElementById('open-command-palette');
return trigger instanceof HTMLButtonElement && !trigger.disabled && !trigger.hidden
? trigger
: document.getElementById('command-workflow');
},
})"
>
<button
id="open-command-palette"
class="lyra-cmdk-trigger"
type="button"
aria-label="Search commands"
@click="open = true"
>
Search commands
</button>
<h2 id="command-workflow" tabindex="-1">Command workflow</h2>
<div class="lyra-cmdk-overlay" x-bind="overlay" style="display: none">
<div class="lyra-cmdk" x-bind="panel">
<div class="lyra-cmdk__search">
<input x-bind="search" />
<kbd class="lyra-kbd">esc</kbd>
</div>
<div class="lyra-cmdk__body" x-bind="list">
<p class="lyra-cmdk__empty" x-bind="empty" x-text="emptyText()"></p>
</div>
<div class="lyra-cmdk__footer">
<button class="lyra-btn lyra-btn--ghost lyra-btn--md" type="button" @click="open = false">
Close
</button>
</div>
</div>
</div>
</div><lyra:command-palette> Gerado do lyra-ds/blade v0.10.0.
O comportamento vem de lyraCommandPalette() — instale @lyra-ds/alpine e veja a aba HTML + Alpine.
| Prop | Padrão | Obrigatória | Valores de exemplo |
|---|---|---|---|
groups | [] | — | — |
defaultOpen | false | — | — |
placeholder | null | — | — |
emptyMessage | null | — | — |
searchLabel | null | — | — |
hints | [] | — | — |
hotkey | 'k' | — | — |
inline | false | — | — |
label | null | — | — |
<lyra:command-palette
placeholder="Type a command or search…"
empty-message="No results found."
hotkey="k"
:groups="[
['label' => 'Navigation', 'items' => [
['id' => 'go-projects', 'label' => 'Go to projects'],
['id' => 'go-billing', 'label' => 'Go to billing'],
]],
['label' => 'Actions', 'items' => [
['id' => 'new-project', 'label' => 'Create a project'],
]],
]"
/>