AuroraUI.Components.Command (Aurora UI v0.1.0)

View Source

Command family — search field, search results, and the command palette.

These components cover the "find and act" surface of an app: a debounced search input, an accessible results list with a live result-count announcement and an empty state, and a command palette opened from a visible trigger (never a keyboard shortcut alone).

Semantics

Search is server-rendered: the input emits phx-change/phx-submit and the server re-renders search_results/1. JavaScript only enhances — the palette's filtering/keyboard behavior is layered on top of a fully working button + form. Results are a semantic list (<ul role="list">), not a listbox: results are navigable content (links), not options in a single-select widget, so the list-plus-link semantics match assistive-technology expectations and keep native link keyboard behavior. The palette is the one exception — it is a filter-then-pick widget, so it uses the combobox + listbox pattern.

Debounce guidance

Always debounce the live search so you do not round-trip on every keystroke. Set debounce (renders phx-debounce); 120300 ms feels responsive without flooding the server, and "blur" defers until focus leaves. Pair a phx-change for incremental results with a phx-submit fallback so pressing Enter still works when JS is unavailable.

Summary

Functions

A command palette opened from a visible trigger button.

A search input inside a role="search" landmark.

A single result row. Becomes a link when navigate/patch/href is set, otherwise a static <li> (wire phx-click for command-style rows).

A results list with a polite result-count announcement and an empty state.

Functions

command_palette(assigns)

A command palette opened from a visible trigger button.

The palette is a dialog (role="dialog", aria-modal, labelled by its title) containing a combobox search input and grouped commands in a listbox. Enhancement is lazy: phx-hook="AuroraCommandPalette" dynamic-imports ./command.js only where a palette actually renders, keeping it out of every other bundle.

Shortcuts are an enhancement, never the only door

The visible trigger is the source of truth, so the palette works with zero keyboard shortcuts. When you do wire one (via shortcut), it must be:

  • discoverable — shown as a kbd hint on the trigger and inside the dialog, so users can learn it;
  • configurable — the value is an attribute, not hard-coded;
  • non-hijacking — the hook must never intercept browser or assistive-technology shortcuts (it listens for the configured combo only, and never swallows keys while focus is in another input or AT is driving).

Examples

<.command_palette id="cmdk" shortcut="⌘K">
  <:group label="Navigation">
    <button role="option" data-aui-command-item phx-click="go" phx-value-to="/inbox">
      Go to Inbox
    </button>
  </:group>
  <:empty>No commands match.</:empty>
</.command_palette>

Attributes

  • id (:string) - stable id — required for the hook to survive patches. Defaults to nil.
  • label (:string) - dialog accessible name (title). Defaults to "Command palette".
  • trigger_label (:string) - visible trigger button text. Defaults to "Search commands".
  • placeholder (:string) - Defaults to "Type a command or search…".
  • open (:boolean) - server-controlled open state. Defaults to false.
  • shortcut (:string) - discoverable, configurable shortcut hint (e.g. "⌘K"); shown as a kbd and passed to the hook. Defaults to nil.
  • Global attributes are accepted.

Slots

  • group - a titled group of commands; place role=option items inside. Accepts attributes:
    • label (:string) (required)
  • empty - shown when the filter matches nothing.

search_field(assigns)

A search input inside a role="search" landmark.

Renders type="search" + explicit role="searchbox", a leading search icon, an optional clear button (auto-hidden while empty via :placeholder-shown), and a busy state (aria-busy) with a spinner. Emits phx-change/phx-submit from the wrapping form.

Examples

<.search_field phx-change="search" phx-submit="search" debounce={200} value={@q} />
<.search_field label="Find a doc" loading={@searching} name="query" />

Attributes

  • id (:string) - stable id; generated when omitted. Defaults to nil.
  • name (:string) - input name submitted with the form. Defaults to "q".
  • value (:string) - current query value. Defaults to nil.
  • label (:string) - accessible name for the search region and input. Defaults to "Search".
  • placeholder (:string) - Defaults to "Search…".
  • size (:string) - Defaults to "md". Must be one of "sm", "md", or "lg".
  • loading (:boolean) - sets aria-busy and shows a spinner. Defaults to false.
  • clearable (:boolean) - renders a clear button when the field has text. Defaults to true.
  • debounce (:any) - renders phx-debounce (ms integer or "blur"); always debounce live search. Defaults to nil.
  • Global attributes are accepted. Supports all globals plus: ["phx-change", "phx-submit", "phx-target", "phx-value-id", "autocomplete", "method", "action"].

Slots

  • inner_block - optional trailing controls rendered after the input (e.g. a scope select).

search_result(assigns)

A single result row. Becomes a link when navigate/patch/href is set, otherwise a static <li> (wire phx-click for command-style rows).

Attributes

  • navigate (:string) - Defaults to nil.
  • patch (:string) - Defaults to nil.
  • href (:string) - Defaults to nil.
  • active (:boolean) - highlights the current/keyboard-focused row. Defaults to false.
  • Global attributes are accepted. Supports all globals plus: ["phx-click", "phx-value-id", "target", "rel", "download"].

Slots

  • icon - leading media/icon (decorative).
  • meta - trailing metadata (shortcut, type, timestamp).
  • inner_block (required) - the result title/summary.

search_results(assigns)

A results list with a polite result-count announcement and an empty state.

When count is 0 the empty slot renders (a default message otherwise); any other value renders the results. The count is announced through a visually-hidden aria-live="polite" region so screen-reader users hear how many matches came back without moving focus. Use the group slot for titled sections, or the default slot for a flat list of search_result/1.

Examples

<.search_results count={length(@results)}>
  <.search_result :for={r <- @results} navigate={~p"/docs/#{r.slug}"}>
    {r.title}
  </.search_result>
  <:empty>
    No matches. Try <.search_result>installation</.search_result>.
  </:empty>
</.search_results>

Attributes

  • id (:string) - Defaults to nil.
  • label (:string) - accessible name for the results list. Defaults to "Search results".
  • count (:integer) - number of results; drives the aria-live announcement and the empty state. nil = unknown. Defaults to nil.
  • loading (:boolean) - Defaults to false.
  • Global attributes are accepted.

Slots

  • group - an optional titled group of results. Accepts attributes:
    • label (:string) (required)
  • inner_block - flat results (search_result/1) when not grouping.
  • empty - no-results content; put suggested queries/actions here.