A scoped entity-search overlay — a sibling to the command palette, but for navigating to records rather than firing actions. Set entities + scopes; it filters client-side, shows per-scope counts, highlights matches, groups by scope, and emits events the consumer handles.
Data is set as properties in a script. Open with the button or /. Try a query (e.g. “ri”) to see counts + highlighting; ← / → change the scope; ⌘/Ctrl + Enter fires show-all.
The searchable records, set as a property. Each: { id, title, subtitle?, scope, icon?, url?, meta? }. Filtered client-side.
scopes
EsaSearchScope[]
[]
Facets to narrow by. Each: { id, label, icon? }. An implicit "All" facet is prepended.
recent
EsaSearchEntity[]
[]
Shown when the query is empty and the "All" facet is active.
row-actions
EsaSearchRowAction[]
[]
Per-row secondary actions (e.g. Impersonate). Each: { id, label, icon?, scopes? }. scopes restricts which rows show it. Property: `rowActions`.
open
boolean
false
Reflected open state.
placeholder
string
'Search…'
Input placeholder.
all-label
string
'All'
Label for the implicit all-scopes facet. Property: `allLabel`.
hotkey
'' | 'mod+k' | 'slash'
''
Built-in open shortcut. 'mod+k' = Cmd/Ctrl+K toggle; 'slash' = '/' opens (ignored while typing in a field).
Events
Event
Type
Default
Description
select
{ entity }
—
A row was chosen (click or Enter). The consumer navigates (e.g. to entity.url).
scope-change
{ scope }
—
The active facet changed. scope is '' for All.
show-all
{ query, scope }
—
Cmd/Ctrl+Enter — "see all results". The consumer routes to a full results page.
row-action
{ action, entity }
—
A per-row action button was clicked. action is the EsaSearchRowAction id.
Methods
Prop
Type
Default
Description
show()
method
—
Opens, clears the query + scope, focuses the input.
close()
method
—
Closes the overlay.
toggle()
method
—
Toggles open state.
Accessibility
A native modal <dialog>, opened with showModal() — so the page behind it is genuinely inert, focus is contained, Esc closes, and focus returns to the trigger. None of that is our code.
The input is a role="combobox" driving a role="listbox" of role="option" rows via aria-activedescendant. The rows are <div>s, not buttons: an option may not be an interactive widget, and while they were buttons the per-row action buttons were nested inside them.
ArrowUp / ArrowDown move the active row (wrapping across groups), Enter selects, ⌘/Ctrl + Enter fires show-all, Esc closes.
Facets are a role="radiogroup" navigated with ← / →. Until 2026-08-18 they claimed role="tablist" and bound Tab instead — the exact inversion of what the role promises, and it cost the per-row action buttons any keyboard route at all (SC 2.1.1, Level A). Tab is now plain Tab.