Search
Global search lives in the top nav. The trigger is a default-sized outlined button that opens a modal. Results stream into the modal via Alpine-AJAX. Same shape across every viewport — full-screen on mobile, centred card on sm+.
Trigger button
Outlined surface (white over warm-cream nav), default button height (px-4 py-2),
with a leading search icon, "Search…" label, and a trailing / kbd hint.
Mobile collapses to the icon-only variant on the right.
<!-- Desktop trigger (md+) -->
<button type="button" x-on:click="openSearch()"
class="hidden md:inline-flex items-center gap-2 w-64 lg:w-72 rounded-lg
border border-border-default bg-white dark:bg-gray-800 px-4 py-2
text-left text-gray-500 dark:text-gray-400
hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
<koala-icon name="Search" size="Small" class="shrink-0" />
<span class="flex-1">Search…</span>
<kbd class="...">/</kbd>
</button>
<!-- Mobile trigger (icon only) -->
<button type="button" x-on:click="openSearch()"
class="rounded-lg p-2 text-gray-500 hover:bg-gray-100
dark:text-gray-400 dark:hover:bg-gray-700 sm:p-2.5 md:hidden"
aria-label="Search">
<koala-icon name="Search" class="h-5 w-5" />
</button>
Opening the modal
Three ways to open the search modal — all route through the same openSearch() Alpine method.
- Click Click either trigger button (desktop button or mobile icon).
-
/
Press
/from anywhere on the page (skipped when typing in an input/textarea/select). - ⌘ K Cmd+K (Mac) or Ctrl+K (Windows/Linux) — works even when an input is focused.
Modal
Centred card on sm+ at 10vh from the top, full-screen below sm.
Header with title + description and a close button. Search input below with a debounced
AJAX request (300 ms) into the results region. ESC, backdrop click, and the close button all dismiss.
Search
Quotes, transactions, partners — by reference, name, email, or address.
<div x-show="searchOpen" x-on:click.self="close()" x-on:keydown.escape.window="close()"
class="fixed inset-0 z-50 flex items-start justify-center sm:pt-[10vh] sm:p-4">
<div class="bg-white dark:bg-gray-800 sm:border border-border-default
rounded-none sm:rounded-xl w-full h-full sm:h-auto sm:max-w-2xl
sm:max-h-[80vh] flex flex-col">
<!-- Header: title + description + close -->
<!-- Body: search input -->
<input x-ref="searchModalInput" x-model="query"
x-on:input.debounce.300ms="doSearch()" ... />
<!-- Results: streamed via Alpine-AJAX into #search-results-modal -->
<div x-show="showResults" x-cloak class="flex-1 overflow-y-auto">
<div id="search-results-modal"></div>
</div>
</div>
</div>
Keyboard navigation in results
Once results render, arrow keys move between them and Enter activates the focused result.
Result rows have data-result; the highlighted row gets
data-kb-active.
- ↑ / ↓ Move between results
- Enter Activate the highlighted result
- Esc Close the modal
Alpine state
The search lives on the navbar's root x-data scope so the trigger,
modal, and results all share state.
| Property / method | Purpose |
|---|---|
| searchOpen | Whether the modal is visible |
| query | Two-way bound to the modal's search input |
| loading | Spinner state during the AJAX request |
| showResults | Whether the results region is rendered |
| activeIndex | Index of the keyboard-highlighted result row |
| openSearch() | Sets searchOpen = true and focuses the modal input |
| doSearch() | Debounced AJAX request that streams results into #search-results-modal |
| navigate(dir, id) | Move activeIndex up/down within a results container |
| selectActive(id) | Click the currently-highlighted result row |
| close() | Resets searchOpen, showResults, and activeIndex |