Heartwood Health · How to Build

01 · Stat Counter.

← components.html
all how-tos
01
Stat Counter

Headline metric · animated count-up

A single number that frames the page or a section. Pair with one delta line so the reader knows whether the trend is good or bad.

When to use

One number that summarizes the whole story — total volume, total count, headline KPI. Avoid for raw percentages without context (use a delta line instead) and for comparisons of more than one value (those need a chart).

How it animates

A requestAnimationFrame loop eases the displayed number from 0 to target over 1.6 seconds using easeOutCubic. The IntersectionObserver gates the start — the count fires only when the tile reaches 20% viewport.

Data shape
statCounter: {
  title:          'Patients seen',
  subtitle:       'this quarter',
  value:          12847,    // integer target
  deltaPct:       8,        // % vs comparison period
  deltaLabel:     'over last quarter',
  deltaDirection: 'up',     // 'up' = ▲ teal · 'down' = ▼ coral
}
Build pattern
const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
function tick(now) {
  const t = Math.min(1, (now - start) / 1600);
  el.textContent = Math.round(target * easeOutCubic(t))
                       .toLocaleString();
  if (t < 1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
⚠ Watch

Don't fire the counter on page load. Without IntersectionObserver gating, off-screen tiles burn through their animation before the user scrolls to them — by the time they arrive, the number is already at its final value. Always gate on .visible.