Koala logo Design

Stat cards

Dashboard stat cards display key metrics. Used on the partner and conveyancer home pages with period filters (see Tabs for the period filter pattern).

Basic stat cards

Large number with a label below. Used on the partner dashboard for quick stats like new quotes, active transactions, and referral fees. These are links that navigate to the relevant list page.

<div class="flex justify-around py-6 lg:py-12 text-center">
    <a href="@(ListQuotes.Route())?Status=New"
       x-target.push="main" class="flex-1">
        <span class="text-3xl font-bold text-gray-900
                    dark:text-white">@Model.NewQuoteCount</span>
        <span class="block mt-1 text-sm text-gray-500
                    dark:text-gray-400">New quotes</span>
    </a>
    <a href="@(ListTransactions.Route())?Status=Active"
       x-target.push="main" class="flex-1">
        <span class="text-3xl font-bold text-gray-900
                    dark:text-white">@Model.ActiveTransactionCount</span>
        <span class="block mt-1 text-sm text-gray-500
                    dark:text-gray-400">Active transactions</span>
    </a>
    <div class="flex-1">
        <span class="text-3xl font-bold text-gray-900
                    dark:text-white">@Model.ReferralFeesTotal.ToString("C0")</span>
        <span class="block mt-1 text-sm text-gray-500
                    dark:text-gray-400">Referral fees</span>
    </div>
</div>

Stat cards with trends

Used on the conveyancer dashboard. Each card shows a badge label, a large count, and a trend indicator (up/down/neutral). The trend compares the current period to the previous period.

Quotes

42 total +5
New
18 +3
Accepted
14 +2
Expired
7 -1
Cancelled
3 0
<div koala-card koala-card-flush>
    <div class="flex items-baseline gap-3 px-4 pt-4 pb-2
                sm:px-6 sm:pt-6 sm:pb-2">
        <h3 class="text-lg font-semibold text-gray-900
                    dark:text-white">Quotes</h3>
        <span class="text-sm font-normal text-gray-500
                    dark:text-gray-400">@Model.QuoteCurrentTotal total</span>
        <!-- Trend indicator -->
        @if (Model.QuoteTotalChange > 0)
        {
            <span class="flex items-center text-sm
                        text-green-500 dark:text-green-400">
                <!-- Arrow up SVG --> +@Model.QuoteTotalChange
            </span>
        }
    </div>
    <div class="grid grid-cols-2">
        <a href="..." x-target.push="main" class="block p-4 sm:p-6">
            <div class="mb-2">
                <span koala-badge="Neutral">New</span>
            </div>
            <div class="flex items-baseline gap-2">
                <span class="text-2xl font-bold leading-none text-gray-900
                            sm:text-3xl dark:text-white">@count</span>
                <span class="inline-flex items-center text-sm
                            text-green-500 dark:text-green-400">
                    <!-- Arrow up SVG --> +@change
                </span>
            </div>
        </a>
    </div>
</div>

Trend indicators

Three states: positive (green, arrow up), negative (red, arrow down), and neutral (gray, arrow right).

+5 (positive) -3 (negative) 0 (neutral)
<!-- Positive (green, arrow up) -->
<span class="flex items-center text-sm
            text-green-500 dark:text-green-400">
    <svg class="w-4 h-4" ...>
        <path d="m5 12 7-7 7 7"></path>
        <path d="M12 19V5"></path>
    </svg>
    +@Model.Change
</span>

<!-- Negative (red, arrow down) -->
<span class="flex items-center text-sm
            text-red-500 dark:text-red-400">
    <svg class="w-4 h-4" ...>
        <path d="M12 5v14"></path>
        <path d="m19 12-7 7-7-7"></path>
    </svg>
    @Model.Change
</span>

<!-- Neutral (gray, arrow right) -->
<span class="flex items-center text-sm
            text-gray-400 dark:text-gray-500">
    <svg class="w-4 h-4" ...>
        <path d="M5 12h14"></path>
        <path d="m12 5 7 7-7 7"></path>
    </svg>
    0
</span>

Stat cards with sparklines

The conveyancer dashboard renders one large sparkline per card using custom smooth SVG curves. Sparkline data is passed via data-sparkline attributes on a container div. The line colour is a single neutral #9CA3AF across all three cards so it doesn't compete with the status pills underneath.

Quotes

42 +6
New 12 Accepted 14 Expired 9 Cancelled 7
<!-- One sparkline per card (full-width, large) -->
<div class="w-full h-28 sm:h-32 xl:h-40 mb-4"
     data-sparkline="@Json.Serialize(Model.QuoteCreatedSparkline)"
     data-sparkline-labels="@Html.Raw(...)"
     data-sparkline-title="Newly created"
     data-sparkline-color="#9CA3AF"
     data-sparkline-scale-group="dashboard-created"
     data-sparkline-axes="true">
</div>

<!-- Single neutral colour for all three cards -->
const string sparklineLineColor = "#9CA3AF";

Responsive grid layout

The conveyancer dashboard uses three equal cards in a xl:grid-cols-3 layout (Quotes, Transactions, Partners). On smaller screens it stacks to 2 columns at lg, then 1 column.

Quotes

Total + sparkline + status pills

Transactions

Total + sparkline + status pills

Partners

Total + sparkline + status pills

<div class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
    <div koala-card>
        <!-- Quotes: heading + total + sparkline + status pills -->
    </div>
    <div koala-card>
        <!-- Transactions: heading + total + sparkline + status pills -->
    </div>
    <div koala-card>
        <!-- Partners: heading + total + sparkline + status pills -->
    </div>
</div>

Period filter integration

Stat cards are paired with a period filter. The filter defaults to 30 days and updates the stats via Alpine-AJAX. The selector and the figures it drives are wrapped together in a single koala-card with the selector centred above the totals — visually scoping the period to the figures it actually drives. Sections below the card (recent quotes, active transactions, etc.) are deliberately period-independent.

Home
12 New quotes
8 New transactions
£4,200 Referral fees
<div koala-card>
    <div class="flex justify-center mb-10">
        <div class="flex items-center gap-1">
            @foreach (var (key, label) in new[] {
                ("7d", "7d"), ("30d", "30d"),
                ("3m", "3m"), ("12m", "12m") })
            {
                var isActive = activePeriod == key;
                <a href="@(IndexModel.Route())?period=@key"
                   x-target.push="home"
                   class="px-3 py-1.5 rounded-lg text-sm font-medium
                          @(isActive
                              ? "bg-gray-900 text-white ..."
                              : "text-gray-500 hover:bg-gray-100 ...")">
                    @label
                </a>
            }
        </div>
    </div>
    <div class="flex justify-around text-center mb-4">
        <!-- KPI tiles -->
    </div>
</div>