Koala logo Design

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.

Results stream in here as the user types.
<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
searchOpenWhether the modal is visible
queryTwo-way bound to the modal's search input
loadingSpinner state during the AJAX request
showResultsWhether the results region is rendered
activeIndexIndex 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