Koala logo Design
No matches for “
↑↓ navigate open Esc close
Components Mega search

Mega search

The shared workspace mega-search chrome — the top-bar search box, page scrim, and results bubble, wired to one shared keyboard-nav script so the Portal and this site can't drift.

<koala-mega-search>
This page has no standalone live demo — the helper hardcodes the #ws-form and #ws-results ids, so a second instance would collide with the search box already mounted in this page's own top bar. That top-bar search is the live demo: press / right now to try it.
<koala-mega-search action="/search" placeholder="Search quotes, transactions, clients, branches…">
    <input type="hidden" name="handler" value="Workspace" />
    <input type="hidden" name="area" value="conveyancing" />
</koala-mega-search>

Mounted once, in the app layout's top bar — never on an individual page. The component renders an Alpine-AJAX GET form; each app supplies its own search endpoint via action and its own results partial, which morphs into the results bubble. Hidden inputs passed as child content ride along on every request, so each app scopes its own query — the Portal scopes by handler and area; this site's top bar passes none and simply GETs /component-search.

The helper renders the whole shell; the .ws-* chrome classes live in the shared tokens.css and the behaviour in the shared workspace-search.js Alpine object (workspaceSearch). Apps never write any of this themselves:

  • Trigger input (.ws-box, #ws-form) — the visible search box: leading search icon, the input itself, and a / keyboard hint. Focusing it opens the search and fires an immediate empty-query request; typing re-queries with a 200ms debounce. Focus styling sits on the box, matching a standard Koala input.
  • Scrim (.ws-scrim) — dims the page behind open results on desktop, like a modal backdrop. Below md the box collapses to an icon button and opening expands a full-screen overlay instead (with its own back button).
  • Results panel (.ws-bubble) — the floating dialog under the box, containing the #ws-results morph target the app's results partial swaps into (x-merge="morph", so the highlight doesn't flicker between keystrokes).

Keyboard behaviour (shared, app-agnostic): / opens the search from anywhere on the page (ignored while typing in a field, so "/" stays typeable); move the row highlight; Enter activates the highlighted row; Esc clears the query first, then closes; clicking outside closes. There is no ⌘K binding in the shared script — this site's mobile-only ⌘K palette is separate chrome. Programmatic open is a workspace-search:open window event, optionally carrying a detail.scope that pre-sets the app's #ws-type hidden field.

Results contract — the app's results partial must render the shape the keyboard nav expects: the #ws-results morph target containing a [data-ws-list] scroller, a [data-ws-highlight] overlay, and [data-row] rows. Row styling stays app-local because the data differs; everything around the rows is shared.

<div id="ws-results">
    <div data-ws-list class="(app-local scroller)">
        <div data-ws-highlight class="(app-local highlight overlay)"></div>
        <a data-row href="/quotes/…">First result row</a>
        <a data-row href="/quotes/…">Second result row</a>
    </div>
</div>
4 attributes
Attribute Values Notes
action URL Required. The form action — the app's search endpoint. The form GETs it via Alpine-AJAX on focus and on every (debounced) keystroke.
placeholder string Defaults to Search…. Input placeholder text.
results-id string Defaults to ws-results. The morph-target id the form swaps into; the app's results partial must render the same id.
aria-label string Defaults to Workspace search. Accessible label on the results dialog.

Child content is rendered inside the form as hidden fields (handler, area, type, …) so each app can scope its own query — see the Canonical example.

<koala-mega-search action="/search" … />
Do One instance per layout, mounted in the app chrome (the top bar). Every page gets it for free — including this one.
<form id="ws-form">…
<form id="ws-form">…
Don't Don't mount a second instance on a page, and don't hand-roll the .ws-* chrome. The form and results ids are hardcoded, so two instances collide; hand-rolled chrome drifts from the shared keyboard nav.