ComponentsStat Tile
Stat Tile
componentA KPI tile — big number, label, delta and an optional sparkline.
Recurring revenue
This month at a glance.
Monthly recurring
$48,210
12.4%
Active users
8,431
4.1%
Churn rate
2.8%
0.6%
Composes: Card · Sparkline · Badges & pills
Recipe CSS
This is the exact CSS your kit ships for Stat Tile — token-driven, so it re-themes with every knob. Get it (and the rest) from the configurator's Use kit panel.
/* === Stat tile (#111) ====================================================
* Premium metric tile pattern. Two tiles fit in one gallery card; each tile
* stacks: eyebrow label + icon box (top row) → big tabular number → sparkline
* + delta pill (foot row). Sparkline draws in on mount via stroke-dasharray. */
.stat-tile-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--k-s-10);
}
.stat-tile {
display: flex;
flex-direction: column;
gap: var(--k-s-8);
padding: var(--k-space, 16px);
background: var(--k-surface-2);
border-radius: var(--k-radius-md);
border: var(--k-hairline, 1px solid var(--k-border));
box-shadow: var(--k-shadow-tactile);
min-width: 0;
}
.stat-tile__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--k-s-6);
}
.stat-tile__label {
font-size: var(--k-type-caption);
font-weight: var(--k-weight-medium);
letter-spacing: var(--k-track-eyebrow);
text-transform: uppercase;
color: var(--k-fg-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.stat-tile__icon {
flex-shrink: 0;
width: 22px;
height: 22px;
display: grid;
place-items: center;
border-radius: var(--k-radius-sm);
}
.stat-tile__icon--primary { background: var(--k-primary-soft); color: var(--k-primary-soft-fg); }
.stat-tile__icon--accent { background: var(--k-secondary-soft); color: var(--k-secondary-soft-fg); }
.stat-tile__icon svg { width: 12px; height: 12px; }
.stat-tile__value {
font-size: var(--k-type-h2);
font-weight: var(--k-weight-semibold);
font-family: var(--k-font-display);
font-variant-numeric: tabular-nums;
letter-spacing: var(--k-track-display);
line-height: var(--k-leading-tight);
color: var(--k-fg);
}
/* CP3 — the HERO stat (confident-pro gap #1: one focal point per surface). The
* lead KPI spans the full grid and renders its value at the display tier (~48px),
* so a dashboard reads as a bento with one feature metric instead of a uniform
* 2×2 of equals. The number earns its focus through SIZE, not colour — accent
* stays reserved for the one action (CP2). The Linear/Stripe insight move. */
.stat-tile--hero { grid-column: 1 / -1; }
.stat-tile--hero .stat-tile__value {
font-size: var(--k-type-display);
letter-spacing: -0.03em;
line-height: 1;
/* CP6 — deploy the dormant CP1 entrance: the hero KPI rises + fades + settles
on mount (the "the number arrives" Linear/Stripe beat). --k-anim-rise is
'none' at Motion=None and the global reduced-motion guard snaps it instant
(k-rise uses both-fill, so it lands on the visible end-state, never stuck). It
replays only on remount / Motion change, not on every config tweak. */
animation: var(--k-anim-rise);
}
.stat-tile__foot {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--k-s-8);
}
.stat-tile__spark {
width: 60%;
max-width: 80px;
height: 22px;
flex-shrink: 1;
}
.stat-tile__spark path {
stroke-dasharray: 200;
stroke-dashoffset: 200;
animation: stat-spark-draw 600ms cubic-bezier(.05, .7, .1, 1) both;
animation-delay: 240ms;
}
@keyframes stat-spark-draw {
to { stroke-dashoffset: 0; }
}
.stat-tile__delta {
display: inline-flex;
align-self: flex-start; /* hug content — don't stretch to the column's full width */
align-items: center;
gap: var(--k-s-2);
padding: var(--k-s-2) var(--k-s-6);
border-radius: 999px;
font-size: var(--k-type-eyebrow);
font-weight: var(--k-weight-semibold);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.stat-tile__delta svg { flex-shrink: 0; }
/* Delta tone modifiers — avoid per-instance inline colours. */
.stat-tile__delta--up { color: var(--k-success); background: var(--k-success-soft); }
.stat-tile__delta--down { color: var(--k-danger); background: var(--k-danger-soft); }
/* Clickable KPI tile — drills into a detail view (hover lift + a trailing
* chevron that nudges right, "there's more behind this number"). */
.stat-tile--clickable { cursor: pointer; transition: border-color var(--k-dur-fast, 140ms) var(--k-ease, ease), box-shadow var(--k-dur-fast, 140ms) var(--k-ease, ease), transform var(--k-dur-fast, 140ms) var(--k-ease, ease); }
.stat-tile--clickable:hover { border-color: var(--k-state-border); box-shadow: var(--k-shadow-sm); transform: translateY(-1px); }
.stat-tile--clickable:focus-visible { outline: 2px solid var(--k-ring, var(--k-primary)); outline-offset: 2px; }
.stat-tile__drill { margin-left: auto; display: inline-flex; color: var(--k-fg-faint); transition: transform var(--k-dur-fast, 140ms) var(--k-ease, ease); }
.stat-tile--clickable:hover .stat-tile__drill { transform: translateX(2px); color: var(--k-fg-muted); }
@media (prefers-reduced-motion: reduce) { .stat-tile--clickable, .stat-tile__drill { transition: none; } }
/* Bare metric strip — joined cells in one box with internal hairlines (was
* .statgrp). Reuses .stat-tile__value / .stat-tile__label; no per-cell card. */
.stat-tile-strip {
/* Responsive: auto-fit as many equal columns as the width holds, wrapping
* cleanly. The grid GAP shows the container line as the internal hairline —
* so dividers need no nth-child math and survive any column count. */
display: grid;
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
gap: var(--k-bw, 1px);
background: var(--k-border);
border: var(--k-hairline, 1px solid var(--k-border));
border-radius: var(--k-radius-md);
overflow: hidden;
}
.stat-tile-strip__cell { padding: var(--k-space, 16px); background: var(--k-surface); display: flex; flex-direction: column; gap: var(--k-s-2); }
/* Summary-band Fill — the ONE focal "state at a glance" strip per screen wears
* the tactical wash (flagship doctrine): cells take --k-surface-fill, the grid
* gap stays the hairline. White (default) Background ⇒ fill resolves to plain
* --k-surface, so the modifier is a no-op until a tint is chosen. */
.stat-tile-strip--fill .stat-tile-strip__cell { background: var(--k-surface-fill); }