<!-- GENERATED by scripts/build-llms.mjs from llms/layout.md — do not edit this file. -->

# `lr-command-palette`

- **Import** `import '@aceshooting/lyra-ui/components/lr-command-palette.js';` (stable tag alias; registers the tag)
- **Class** `LyraCommandPalette`, also available unregistered from `@aceshooting/lyra-ui/components/layout/command-palette/command-palette.class.js`
- **Family** `components/layout/` — see `llms/index.md` for its siblings
- **Status** `stable` since `4.0.0` — see the maturity and deprecation policy in `llms/shared.md`
- **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
- **Deprecations** none
- **Optional peers** none
- **Themeable via** 15 parts, 12 custom properties — see this component's own `@csspart`/`@cssprop` list below
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`

---

## `lr-command-palette`

Searchable application command menu. Renders nothing at all while closed. Uses the same shared
overlay infrastructure as `lr-dialog` (focus-trapping Tab, Escape dismissal, backdrop-click
dismissal, ref-counted document scroll lock).

Valid string keywords still participate in search alongside the command's label, description, and
group. Unsafe keyword entries are skipped without invoking accessors, and selection returns the
original command object.

**Properties:**

- `open: boolean = false` (reflected) — after the initial silent render, property and attribute
  writes use the same synchronous cancelable transaction as `openPalette()`/`close()`; a veto
  restores reflection and prevents query/active-row opening side effects
- `commands: readonly LyraCommand[] = []` (attribute: false) — `{ commandId, label, description?, group?,
shortcut?, keywords?: readonly string[], disabled?, icon?, onSelect? }`. The sequence is copied,
  bounded, and frozen while each command object's identity is retained for `onSelect`; create and
  reassign a new command array after sequence or row changes. `commandId` is a stable business identity and
  must be nonempty and unique; invalid rows are omitted and the first duplicate wins. Replacing or
  reordering the array preserves the active command by `commandId`. `icon` is an optional leading glyph (a `TemplateResult`,
  an emoji string, etc. — not restricted to a square icon) rendered in the `icon` part before the
  label; a command with no `icon` renders no `icon` part at all. A runtime `keywords` value that is
  not an array is ignored, as are non-string members, without dropping otherwise-valid commands.
  Filtering is case-insensitive
  substring matching over `label` + `description` + `group` + `keywords` joined together (not
  fuzzy/subsequence), memoized per `commands` array identity — reassign the array, never mutate it
  in place. Consecutive commands sharing a `group` render one `[part='group']` heading, so pre-sort
  by group yourself.
- `hotkey: string = 'mod+k'` — exact global activation chord parsed as `+`-separated parts; `mod`
  resolves to Cmd on Mac and Ctrl elsewhere. Detection prefers Client Hints, then falls back to
  the legacy platform string and reduced user-agent string rather than trusting
  `navigator.platform` alone. Repeats, composition keys, and extra modifiers do not match. If
  several connected palettes use the same chord, the last connected palette owns it;
  activation is idempotently open rather than a toggle.
- `accessibleLabel?: string` (attribute `aria-label`) — overrides the localized dialog name.
  Omitting it reads back `undefined` and uses the localized `commandPaletteLabel` default; an
  explicitly empty value is used as-is

**Methods:** `openPalette()` (after an accepted open, clears the query and resets the active row;
no-op if already open),
`close()`, `registerCommand(command)` — appends to `commands` and returns an unregister function.

**Keyboard:** ArrowUp/ArrowDown move the active option, skipping `disabled` rows and clamping (not
cycling) at the ends; the active row is scrolled into view. Enter selects. Hovering a non-disabled
row also makes it active.

**Events:** `lr-open`, `lr-close` (both `detail: null`, cancelable — fired before the
mutation, `preventDefault()` keeps the palette in its current open state), `lr-select`
(`detail: { command }`, fired before the command's own `onSelect` runs and before the palette
closes), and no-detail `focus`/`blur` events re-dispatched from the host whenever the search input
gains or loses focus. The `focus`/`blur` bridge is new in 10.0.0: native `focus`/`blur` neither
bubble nor cross the shadow boundary, so a host-level `el.addEventListener('focus', …)` previously
never fired at all.

**Slots:** none.

**CSS parts:** `backdrop`, `dialog` (the `role="dialog" aria-modal="true"` panel), `search` (the
input row), `input` (the `type="search"` field), `clear-button` (clears the search field,
replacing the native search-cancel glyph the component resets; rendered only while it has a
value), `list` (the `role="listbox"`), `group` (a group
heading), `command-group` (a labeled ARIA group of commands), `command` (a `role="option"` button),
`icon` (a command's leading icon glyph; only rendered when the command has one), `label`,
`description`, `shortcut`, `list-spacer` (the virtual result extent), `empty`.

**Themeable custom properties:** `--lr-command-palette-z-index` (default
`var(--lr-overlay-stack-index, var(--lr-layer-modal))`), `--lr-command-palette-offset-block-start`
(default `12vh` — how far down the viewport the dialog sits), `--lr-command-palette-max-inline-size`
(default `var(--lr-size-48rem)`), `--lr-command-palette-max-block-size` (default `70vh`),
`--lr-command-palette-list-max-block-size` (default `50vh` — the scrolling result list), and
`--lr-command-palette-active-bg` (default `var(--lr-color-brand-quiet)` — the background of the
active, keyboard-highlighted command row). That last one is an inline `var()` fallback at the point
of use rather than a `:host` declaration, so it can be set on the element _or on any ancestor_:
`::part(command)[data-active='true']` is invalid CSS (Shadow Parts forbids an attribute selector
after `::part()`), so highlighting the active row previously required hijacking the library-wide
`--lr-color-brand-quiet` token and repainting everything else that read it. Unset, it falls back to
that token, so rendering is unchanged. `--lr-command-palette-search-padding` (default
`var(--lr-space-m)`) and `--lr-command-palette-search-gap` (default `var(--lr-space-s)`) size the
query row; `--lr-command-palette-search-min-height` (default `auto`) and
`--lr-command-palette-search-font-size` (default `inherit`) size the field itself. Point the height
at `--lr-form-control-height-s` (or any tier of that ladder) to match the palette's query field to a
themed search field elsewhere in the application. Unset, all four leave the row exactly as it
shipped.

**Additional API surface:**

- `part="command-group"` — A labeled ARIA group containing visible command options.
- `part="list-spacer"` — Virtual result extent inside the scrolling list.
- `--lr-command-palette-row-height` — Virtual command-row height. Default: `var(--lr-size-3rem)`.
  Its live resolved value drives the painted height, row transforms, keyboard-scroll coordinates,
  and result extent together.
- `--lr-command-palette-group-height` — Virtual group-heading height. Default:
  `var(--lr-size-2rem)`. Its live resolved value drives heading/row transforms and the result extent
  together.
