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.
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).
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.
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
}
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);
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.