Tabs
Tab navigation pattern with count pills, skeleton loading, and period filters.
Tabs use Alpine-AJAX with x-target.push="main"
for partial page updates. The koala-tab-pill
tag helper renders count badges.
Tab navigation
Active tab has a brand-coloured bottom border. Inactive tabs have subtle text with hover states. Each tab can include a count pill.
Details tab content goes here.
Activity tab content goes here.
Notes tab content goes here.
<!-- Active tab -->
<a href="/partner/quotes/view/abc?tab=details"
x-target.push="main"
class="text-brand dark:text-brand-light border-b-2 border-brand
pb-3 text-sm font-medium">
Details
</a>
<!-- Inactive tab with pill -->
<a href="/partner/quotes/view/abc?tab=activity"
x-target.push="main"
x-on:click="showTabSkeleton('tab-container', 'timeline')"
class="text-gray-500 hover:text-gray-700 dark:text-gray-400
dark:hover:text-gray-200 pb-3 text-sm font-medium">
Activity <span koala-tab-pill>12</span>
</a>
<!-- Inactive tab with pill -->
<a href="/partner/quotes/view/abc?tab=notes"
x-target.push="main"
x-on:click="showTabSkeleton('tab-container', 'notes')"
class="text-gray-500 hover:text-gray-700 dark:text-gray-400
dark:hover:text-gray-200 pb-3 text-sm font-medium">
Notes <span koala-tab-pill>3</span>
</a>
Tab count pill
Use koala-tab-pill for count badges inside tab labels.
The tag helper applies consistent sizing and colours.
<span koala-tab-pill>42</span> <!-- Output classes --> inline-flex items-center justify-center px-2 py-0.5 text-xs font-medium rounded-full bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300
Skeleton loading
When a tab is clicked, a skeleton placeholder is shown via
showTabSkeleton(tabsId, type)
(defined in _Layout.cshtml).
The active tab triggers the skeleton immediately, and the AJAX response replaces it.
<!-- Trigger skeleton on tab click -->
<a href="/partner/quotes/view/abc?tab=activity"
x-target.push="main"
x-on:click="@(Model.ActiveTab == "activity"
? "$event.preventDefault()"
: "showTabSkeleton('tab-container', 'timeline')")">
Activity
</a>
<!-- Available skeleton types -->
showTabSkeleton('container-id', 'table') // Cards on mobile, header + rows on desktop
showTabSkeleton('container-id', 'form') // 1-col mobile, 2-col label/value on desktop
showTabSkeleton('container-id', 'timeline') // Filter pill + date header + avatar entries
showTabSkeleton('container-id', 'chart') // Vertical bar chart placeholder
showTabSkeleton('container-id', 'notes') // Add button + note cards with avatar + text
showTabSkeleton('container-id', 'users') // Cards on mobile, avatar table rows on desktop
Skeleton type reference
Each skeleton type matches the content the tab displays.
| Type | Used for | Description |
|---|---|---|
| table | Quotes, Transactions, Partners | Cards on mobile (< XL), header + rows on desktop |
| form | Details, Settings | 1-col on mobile, 2-col label/value grid on desktop |
| timeline | Activity | Filter pill + date header + avatar entries |
| chart | Fees | Vertical bar chart placeholder |
| notes | Notes | Add button + note cards with avatar + text |
| users | Team | Cards on mobile (< SM), avatar table rows on desktop |
Period filter buttons
Pill-style buttons for filtering by time period. Default to 30 days. Standard options: 7 days, 30 days, 3 months, 12 months, All time.
<!-- Active state -->
class="bg-gray-900 text-white dark:bg-white dark:text-gray-900
rounded-lg px-3 py-1.5 text-sm font-medium"
<!-- Inactive state -->
class="text-gray-500 hover:bg-gray-100 dark:text-gray-400
dark:hover:bg-gray-700 rounded-lg px-3 py-1.5 text-sm"
<!-- With Alpine-AJAX (server-driven) -->
<a href="/partner/quotes?period=7d"
x-target.push="main"
class="@(Model.Period == "7d"
? "bg-gray-900 text-white dark:bg-white dark:text-gray-900"
: "text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700")
rounded-lg px-3 py-1.5 text-sm font-medium">
7d
</a>
Active tab click prevention
The currently active tab should prevent navigation by calling
$event.preventDefault().
Inactive tabs trigger a skeleton and navigate via Alpine-AJAX.
<a href="/partner/quotes/view/abc?tab=details"
x-target.push="main"
x-on:click="@(Model.ActiveTab == "details"
? "$event.preventDefault()"
: "showTabSkeleton('tab-container', 'form')")"
class="@(Model.ActiveTab == "details"
? "text-brand dark:text-brand-light border-b-2 border-brand"
: "text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200")
pb-3 text-sm font-medium">
Details
</a>