Count-Up Price Reveal
A copy-paste pricing tables component in pure HTML, CSS & vanilla JS. Zero dependencies, framework-agnostic, MIT-licensed.
Pricing TablesHTMLJavaScriptCSSany framework
Starting at
49/mo
Billed annuallyCopy into your project
HTML
<!-- Count-Up Price Reveal — fades in on scroll, number counts up to target -->
<div class="nuda-pr2-count-up">
<span class="nuda-pr2-count-up__label">Starting at</span>
<div class="nuda-pr2-count-up__price">
<b class="nuda-pr2-count-up__num" data-target="49">0</b><i>/mo</i>
</div>
<span class="nuda-pr2-count-up__sub">Billed annually</span>
</div>JavaScript
/* Count-Up Price Reveal — reveals + counts up once scrolled into view. */
(function () {
var card = document.querySelector('.nuda-pr2-count-up');
if (!card) return;
var numEl = card.querySelector('.nuda-pr2-count-up__num');
var target = Number(numEl.dataset.target || numEl.textContent);
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function run() {
card.classList.add('is-visible');
if (reduce) {
numEl.textContent = target;
card.classList.add('is-done');
return;
}
var start = null;
var duration = 900;
function step(ts) {
if (start === null) start = ts;
var progress = Math.min(1, (ts - start) / duration);
numEl.textContent = Math.round(progress * target);
if (progress < 1) {
requestAnimationFrame(step);
} else {
card.classList.add('is-done');
}
}
requestAnimationFrame(step);
}
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
run();
observer.disconnect();
}
});
}, { threshold: 0.4 });
observer.observe(card);
})();CSS
.nuda-pr2-count-up {
display: flex;
flex-direction: column;
gap: 4px;
padding: 14px;
width: 150px;
opacity: 0;
transform: translateY(10px);
transition: opacity .5s ease,transform .5s ease;
}
.nuda-pr2-count-up.is-visible {
opacity: 1;
transform: translateY(0);
}
.nuda-pr2-count-up__label {
font: 600 9px ui-sans-serif,system-ui;
color: #a1a1aa;
text-transform: uppercase;
letter-spacing: .08em;
}
.nuda-pr2-count-up__price {
display: flex;
align-items: baseline;
gap: 2px;
}
.nuda-pr2-count-up__price i {
font: 500 11px ui-sans-serif,system-ui;
font-style: normal;
color: #63636e;
}
.nuda-pr2-count-up__num {
font: 800 28px ui-sans-serif,system-ui;
color: #fafafa;
font-variant-numeric: tabular-nums;
line-height: 1;
}
.nuda-pr2-count-up__num::before {
content: "$";
}
.nuda-pr2-count-up__sub {
font: 500 10px ui-sans-serif,system-ui;
color: #63636e;
}
.nuda-pr2-count-up.is-done .nuda-pr2-count-up__num {
animation: nuda-pr2-count-up-flash .5s ease;
}
@keyframes nuda-pr2-count-up-flash {
0% {
filter: drop-shadow(0 0 0 rgba(228,255,84,0));
}
40% {
filter: drop-shadow(0 0 10px rgba(228,255,84,.8));
}
100% {
filter: drop-shadow(0 0 0 rgba(228,255,84,0));
}
}
@media (prefers-reduced-motion:reduce) {
.nuda-pr2-count-up {
transition: none;
opacity: 1;
transform: none;
}
.nuda-pr2-count-up.is-done .nuda-pr2-count-up__num {
animation: none !important;
}
}
How to use Count-Up Price Reveal
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.