Overview
Colours
Typography
Icons
Dark mode
Avatar pickers
Avatars
Badges
Breadcrumbs
Buttons
Cards
Empty states
Hoowla link
Nav items
Stat cards
Steppers
Sticky headers
Switchers
Tables
Tabs
Timelines
Charts
Load more
Rich text
Tooltips
Action menus
Forms
Autocomplete
Modals
Notifications
Side trays
Confetti
Guided tour
Period filters
Switchers
Searchable dropdown modals for switching context (partner, branch, organisation). Full-screen on mobile,
positioned below the trigger on desktop with items-start pt-[15vh].
Partner switcher
A modal with a search input and selectable list. The active item shows a checkmark. Each option displays an avatar and name. Used in the sidebar to switch between partners.
Switch partner
No partners found
<div x-data="{ open: false, search: '', selected: 'Koala Legal',
names: ['koala legal', 'greenfield solicitors'] }"
x-on:open-partner-switcher.window="search = ''; open = true;
$nextTick(() => $refs.partnerSearch?.focus())"
x-on:keydown.escape.window="if (open) { open = false; }">
<!-- Backdrop -->
<div x-show="open" x-cloak
class="fixed inset-0 z-40 bg-gray-900/50
dark:bg-gray-900/75"></div>
<!-- Panel -->
<div x-show="open" x-cloak
x-on:click.self="open = false"
class="fixed inset-0 z-50 flex items-center justify-center
sm:items-start sm:pt-[15vh] sm:p-4">
<div class="bg-white dark:bg-gray-800
sm:border border-gray-200 dark:border-gray-700
rounded-none sm:rounded-lg
w-full h-full sm:h-auto sm:max-w-md
overflow-y-auto">
<!-- Header -->
<div class="flex items-center justify-between p-4
border-b border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-900
dark:text-white">Switch partner</h3>
<button type="button" x-on:click="open = false"
class="text-gray-400 hover:text-gray-500
dark:hover:text-gray-300">
<koala-icon name="X" />
</button>
</div>
<!-- Search -->
<div class="p-4 border-b border-gray-200
dark:border-gray-700">
<div class="relative">
<div class="absolute inset-y-0 start-0
flex items-center ps-3
pointer-events-none">
<koala-icon name="Search" size="Small"
class="text-gray-400" />
</div>
<input type="text"
x-ref="partnerSearch"
x-model="search"
placeholder="Search partners..."
class="bg-white dark:bg-gray-700
border border-gray-200
dark:border-gray-600
text-gray-900 dark:text-white
rounded-lg block w-full ps-9
px-3 py-2.5" />
</div>
</div>
<!-- Options -->
<div class="max-h-72 space-y-1 overflow-y-auto p-2">
@foreach (var partner in partners)
{
var isCurrent = partner.Id == currentBranch.PartnerId;
<form method="post" action="/partner/switch"
x-target.push="main"
data-name="@partner.Name.ToLowerInvariant()"
x-show="!search || $el.dataset.name
.includes(search.toLowerCase())">
<input type="hidden" name="partnerId"
value="@partner.Id" />
<button type="submit"
class="flex w-full items-center gap-3
px-3 py-2.5 text-left rounded-lg
@(isCurrent
? "bg-gray-100 ..."
: "text-gray-700 ...")">
<koala-partner-avatar size="Medium"
partner-id="@partner.Id"
name="@partner.Name"
has-avatar="@(...)" />
<span class="truncate flex-1">
@partner.Name
</span>
@if (isCurrent)
{
<!-- Checkmark SVG -->
}
</button>
</form>
}
<!-- Empty state -->
<div x-show="search && !names.some(
n => n.includes(search.toLowerCase()))"
x-cloak
class="px-4 py-6 text-center text-gray-500
dark:text-gray-400">
No partners found
</div>
</div>
</div>
</div>
</div>
Sidebar trigger
The switcher is opened from a trigger in the sidebar header. The trigger shows the current selection with an avatar and a chevron icon. It dispatches a custom window event to open the modal.
<a href="/partner/switch"
x-data
x-on:click="if (!$event.ctrlKey && !$event.metaKey) {
$event.preventDefault();
$dispatch('open-partner-switcher');
}"
class="flex w-full items-center gap-3 rounded-lg
px-2 py-1.5 hover:bg-white/8">
<koala-partner-avatar size="Small"
partner-id="@currentBranch.PartnerId"
name="@currentBranch.PartnerName"
has-avatar="@(...)" />
<div class="min-w-0 flex-1 text-left">
<div class="truncate text-sm text-secondary-300">
@currentBranch.PartnerName
</div>
</div>
<svg class="h-3.5 w-3.5 shrink-0 text-secondary-300">
<path d="M8 9l4-4 4 4m0 6l-4 4-4-4"></path>
</svg>
</a>
Key rules
- Full-screen on mobile, positioned with
sm:items-start sm:pt-[15vh]on desktop - Search input auto-focuses when the modal opens via
$nextTick(() => $refs.search?.focus()) - Options are filtered client-side using
x-showwithincludes() - The active option shows a checkmark icon (
text-fg-brand) - Each option is a
<form>that POSTs to a switch endpoint - Escape key closes the modal via
x-on:keydown.escape.window - Empty state shown when search yields no results
- Trigger uses
$dispatch()to open the modal, with Ctrl/Cmd+Click fallback to full page