Koala logo Design

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.

Quotes 42 Transactions 18 Partners 7
<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>