Prototype Library

Entity Search

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.

Inherited wc import '@esa/ecology/esa-entity-search';

Preview

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.

<button id="open-es">Open entity search</button>

<esa-entity-search id="es" hotkey="slash" placeholder="Search projects, people, documents…"></esa-entity-search>

API

PropTypeDefaultDescription
entities EsaSearchEntity[] [] 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

EventTypeDefaultDescription
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

PropTypeDefaultDescription
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.