Skip to content

KPI Card Variations

snoo edited this page Apr 7, 2026 · 1 revision

KPI Card Variations

A 2x2 KPI grid is a staple of dashboard design. This guide shows how to make the 4 cards feel distinct and purposeful rather than repetitive.


The Standard StatCard

<StatCard
  icon={CreditCard}
  label="TODAY'S REVENUE"
  value="1,870"
  unit="K"
  trend={{ value: "+8.2%", direction: "up" }}
/>

Internal structure:

┌──────────────────────────┐
│  [icon badge]  LABEL     │  ← 12px uppercase tracking-wide
│                          │
│  1,870K                  │  ← 36px bold + 18px unit (2:1)
│                          │
│  +8.2% ↑                │  ← 13px bold, text-success
└──────────────────────────┘

6 Variation Strategies

1. Vary the Icons

Each card should have a unique, semantically meaningful icon. This breaks the visual silhouette:

Metric Icon Why
Revenue Wallet or DollarSign Money container
Orders Package Physical goods
Visitors Users People
Conversion Target Goal/aim
Churn UserMinus User leaving
Growth TrendingUp Direction

2. Vary the Units

Different units create different text rhythm within the 2:1 ratio:

{/* Short unit -- feels compact */}
<StatCard value="3.8" unit="M" />

{/* Medium unit -- balanced */}
<StatCard value="1,870" unit="USD" />

{/* Percentage -- minimal */}
<StatCard value="12.4" unit="%" />

{/* No unit -- just a count */}
<StatCard value="247" />

3. Mix Trend Directions

When all 4 trends point up, the grid looks artificially positive. Real data naturally mixes:

<div className="grid grid-cols-2 gap-4 px-6">
  <StatCard label="REVENUE"    trend={{ value: "+14.2%", direction: "up" }}    ... />
  <StatCard label="CHURN RATE" trend={{ value: "-0.4%",  direction: "down" }}  ... />
  <StatCard label="ARPU"       trend={{ value: "+1.8%",  direction: "up" }}    ... />
  <StatCard label="SUPPORT"    trend={{ value: "+12",    direction: "up" }}    ... />
</div>

A "down" trend uses text-destructive and TrendingDown, naturally creating color contrast.

4. Vary Number Magnitude

Different scales of numbers prevent the grid from feeling uniform:

Card Value Visual Texture
Card 1 3.8M Short, with decimal
Card 2 1,870 Medium, with comma
Card 3 247 Short integer
Card 4 12.4% Decimal percentage

5. Add a Progress Bar to One Card

One card in the grid can include a progress bar below the metric, making it visually taller and heavier:

<div className="bg-card rounded-2xl p-6 shadow-[var(--shadow-card)]">
  <div className="flex items-center gap-2 mb-3">
    <div className="size-7 rounded-lg bg-brand/10 flex items-center justify-center">
      <Target className="size-4 text-brand" />
    </div>
    <p className="text-[12px] text-text-secondary font-medium uppercase tracking-[0.05em]">
      GOAL PROGRESS
    </p>
  </div>
  <p className="text-text-primary text-[36px] font-bold leading-none whitespace-nowrap">
    68<span className="text-[18px] ms-0.5">%</span>
  </p>
  {/* Progress bar adds visual weight */}
  <div className="mt-3 bg-surface-muted rounded-full h-4 overflow-hidden">
    <div className="bg-brand h-full w-[68%] rounded-full" />
  </div>
</div>

6. Use a Segment Bar for Discrete Progress

For discrete metrics (8 out of 10 tasks done), use the segment bar instead of continuous progress:

<div className="mt-3 flex gap-1">
  {[...Array(10)].map((_, i) => (
    <div key={i} className={`h-6 flex-1 rounded ${i < 8 ? 'bg-brand' : 'bg-surface-muted'}`} />
  ))}
</div>

Grid Layout Options

Standard 2x2

<div className="grid grid-cols-2 gap-4 px-6">
  {/* 4 cards */}
</div>

1 + 2 (featured metric + supporting)

<div className="space-y-4 px-6">
  {/* Full-width stat card with progress bar */}
  <StatCard className="col-span-2" ... />
  <div className="grid grid-cols-2 gap-4">
    <StatCard ... />
    <StatCard ... />
  </div>
</div>

3-Column (for smaller metrics)

For information-dense dashboards with 6 mini metrics:

<div className="grid grid-cols-3 gap-3 px-6">
  {/* 3 or 6 compact cards */}
</div>

Color Rules in KPI Grids

Element Color Rule
Icon badge background bg-brand/10 Always 10% opacity of accent
Icon text-brand Always accent color
Value text text-text-primary (#3C3C3C) Never accent color
Up trend text-success (#6B9B7A) Calm green, not vivid
Down trend text-destructive (#D4183D) Only for negative values
Label text-text-secondary (#6A6A6A) Always secondary gray
Unit text-text-primary at smaller size Same color as value, smaller

Common Mistakes

Mistake Fix
All 4 cards use the same icon Use semantically distinct icons
Trend shows "+0.0%" Hide trend icon when value is zero; show "0%" in gray
Giant numbers overflow the card Scale unit up: 18,700,000 -> 1,870K
Adding borders to individual grid cards Cards use shadow only, no borders (light mode)
Making one card a different background color All cards are bg-card (white), no exceptions

Clone this wiki locally