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 mini sparkline charts alongside each stat using ApexCharts. Sparkline data is passed via data-sparkline attributes on a container div.

Accepted
14 +2
<!-- Sparkline container (rendered by ApexCharts) -->
<div class="w-14 sm:w-20 h-8 flex-shrink-0"
     data-sparkline="@Json.Serialize(Model.GetQuoteSparkline(status))"
     data-sparkline-labels="@Html.Raw(...)"
     data-sparkline-title="@status"
     data-sparkline-color="@sparklineColor">
</div>

<!-- Sparkline colours per status -->
QuoteStatus.New       => "#6B7280" (gray)
QuoteStatus.Accepted  => "#059669" (emerald)
QuoteStatus.Expired   => "#D97706" (amber)
QuoteStatus.Cancelled => "#DC2626" (red)

Responsive grid layout

The conveyancer dashboard uses a 5-column grid on XL screens: quotes (2 cols), transactions (2 cols), and fees (1 col). On smaller screens, it stacks to 2 columns then 1 column.

Quotes (2 cols)

Contains a 2x2 grid of status cards

Transactions (2 cols)

Contains a 2x2 grid of status cards

Fees (1 col)

Total fees with trend

<div koala-card koala-card-flush>
    <div class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-5">
        <div class="xl:col-span-2 border-b lg:border-r
                    xl:border-b-0 border-gray-200
                    dark:border-gray-700">
            <!-- Quotes section header + 2x2 status grid -->
        </div>
        <div class="xl:col-span-2 border-b xl:border-b-0
                    xl:border-r border-gray-200
                    dark:border-gray-700">
            <!-- Transactions section header + 2x2 status grid -->
        </div>
        <div class="xl:col-span-1">
            <!-- Fees total + trend -->
        </div>
    </div>
</div>

Period filter integration

Stat cards are always paired with a period filter. The filter defaults to 30 days and updates the stats via Alpine-AJAX. See the Tabs page for the period filter pattern.

Home

12 New quotes
8 Active transactions
£4,200 Referral fees
<div class="flex items-center justify-between gap-3 pb-3 flex-wrap">
    <h2 class="text-2xl font-bold text-gray-900
                dark:text-white">Home</h2>
    <div class="flex items-center gap-1 shrink-0">
        @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>