Skip to content

Count Up

A copy-paste stats & counters component in pure HTML, CSS & vanilla JS. Zero dependencies, framework-agnostic, MIT-licensed.

Stats & CountersHTMLCSSJavaScriptany framework
2847Total users

Copy into your project

HTML
<!-- Count Up — initial value of 0 will animate up to data-target -->
<div class="nuda-count-up">
  <span class="nuda-count-up__value" data-target="2847">0</span>
  <span class="nuda-count-up__label">Total users</span>
</div>
CSS
/* Count Up
   Big stat number with a label. JS does the count-up.
   Customize: --count-text, --count-label */

.nuda-count-up {
  --count-text: #fafafa;
  --count-label: #63636e;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  padding: 12px;
}

.nuda-count-up__value {
  font: 700 2rem ui-sans-serif, system-ui, sans-serif;
  color: var(--count-text);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  background: linear-gradient(180deg, #fafafa, #a1a1aa);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.nuda-count-up__label {
  font: 500 0.625rem ui-sans-serif, system-ui, sans-serif;
  color: var(--count-label);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
JavaScript
/* Count Up — animates from 0 to data-target.
   Triggers when the element scrolls into view. */

(function () {
  var els = document.querySelectorAll('.nuda-count-up__value');
  if (!els.length) return;

  function animate(el) {
    var target = parseInt(el.dataset.target, 10) || 0;
    var duration = 1400;
    var start = performance.now();

    function tick(now) {
      var t = Math.min(1, (now - start) / duration);
      // easeOutCubic
      var eased = 1 - Math.pow(1 - t, 3);
      el.textContent = Math.round(target * eased).toLocaleString();
      if (t < 1) requestAnimationFrame(tick);
    }
    requestAnimationFrame(tick);
  }

  if ('IntersectionObserver' in window) {
    var io = new IntersectionObserver(function (entries) {
      entries.forEach(function (e) {
        if (e.isIntersecting) {
          animate(e.target);
          io.unobserve(e.target);
        }
      });
    }, { threshold: 0.4 });
    els.forEach(function (el) { io.observe(el); });
  } else {
    els.forEach(animate);
  }
})();

How to use Count Up

Paste the HTML where you need it and the CSS into a global stylesheet (or a <style> tag). Every class is prefixed nuda- so it never collides with Tailwind or your own styles. Tweak the CSS custom properties to match your design system.

Works in React, Vue, Svelte, Astro, Next.js, Nuxt, Laravel Blade, Django, Rails — or a single .html file. No npm install, no build step.

More stats & counters components

← Browse all NudaUI components