Admin stat cards with trend badges
A row of KPI cards showing a label, a large value and a coloured up or down trend badge with an accessible description.
Tailwind CSSDashboard widgetsstatskpibadgedashboard
Live preview
Code
<dl class="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
<div class="rounded-xl border border-slate-200 bg-white p-5">
<dt class="truncate text-sm text-slate-500">Revenue</dt>
<dd class="mt-2 flex items-baseline justify-between">
<span class="text-2xl font-semibold tabular-nums text-slate-900">$48,200</span>
<span class="inline-flex items-center gap-1 rounded-full bg-emerald-50 px-2 py-0.5 text-xs font-medium text-emerald-700">
<svg class="size-3" viewBox="0 0 12 12" fill="currentColor" aria-hidden="true"><path d="M6 2 10.5 8h-9L6 2Z"/></svg>
<span class="sr-only">Increased by</span>12.4%
</span>
</dd>
</div>
<div class="rounded-xl border border-slate-200 bg-white p-5">
<dt class="truncate text-sm text-slate-500">New customers</dt>
<dd class="mt-2 flex items-baseline justify-between">
<span class="text-2xl font-semibold tabular-nums text-slate-900">1,284</span>
<span class="inline-flex items-center gap-1 rounded-full bg-red-50 px-2 py-0.5 text-xs font-medium text-red-700">
<svg class="size-3" viewBox="0 0 12 12" fill="currentColor" aria-hidden="true"><path d="M6 10 1.5 4h9L6 10Z"/></svg>
<span class="sr-only">Decreased by</span>3.1%
</span>
</dd>
</div>
<div class="rounded-xl border border-slate-200 bg-white p-5">
<dt class="truncate text-sm text-slate-500">Refunds</dt>
<dd class="mt-2 flex items-baseline justify-between">
<span class="text-2xl font-semibold tabular-nums text-slate-900">37</span>
<!-- a falling refund count is good news, so it is green -->
<span class="inline-flex items-center gap-1 rounded-full bg-emerald-50 px-2 py-0.5 text-xs font-medium text-emerald-700">
<svg class="size-3" viewBox="0 0 12 12" fill="currentColor" aria-hidden="true"><path d="M6 10 1.5 4h9L6 10Z"/></svg>
<span class="sr-only">Decreased by</span>8.0%
</span>
</dd>
</div>
<div class="rounded-xl border border-slate-200 bg-white p-5">
<dt class="truncate text-sm text-slate-500">Active sessions</dt>
<dd class="mt-2 flex items-baseline justify-between">
<span class="text-2xl font-semibold tabular-nums text-slate-900">902</span>
<span class="rounded-full bg-slate-100 px-2 py-0.5 text-xs font-medium text-slate-600">
<span class="sr-only">No change</span>0%
</span>
</dd>
</div>
</dl>
How it works
What it solves
Dashboards open with a few headline numbers, and each needs a trend so readers can tell good from bad. Colour alone fails colour blind users, so this card pairs colour with an arrow and a text alternative.
How it works
The cards sit in a dl inside a responsive grid, using dt for the label and dd for the value, which gives the data real term and definition semantics. The trend badge is a small pill with a green or red palette, an inline arrow SVG that is aria-hidden, and the percentage. A visually hidden span says "Increased by" or "Decreased by", so screen readers hear the direction. A fourth card shows a neutral state in slate for flat metrics. Numbers use tabular-nums so digits line up when values change, and truncate keeps long labels inside the card.
Adapting it
Render the cards from your data in a loop and choose the badge classes by sign. Some metrics are good when they fall, such as refunds; pass a flag so a decrease can still be green. Add a sparkline by placing an SVG in the card's right corner.
Pitfalls
- Format numbers on the server with the user's locale, not in the template.
- Do not hard code the colour from the sign; cost metrics invert it.
- Keep the arrow and the hidden text consistent with the number.
How to use it
- Paste
stat-cards.htmlinside your dashboard content area in a Tailwind 4 project. - Loop your metrics and fill label, value and change.
- Use the green classes for good news and the red classes for bad news, independent of sign.
- Drop the neutral card if you do not track flat values.
Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.