By · Updated

Animated Counter CSS and JS

Count-Up Number Animation in CSS @property or Short JavaScript

No ratings yet

OneTask is free, with no sign-up, and built by one person.
If this tool saved you time, a coffee helps keep new ones coming.

How the Animated Counter works

This animated counter CSS and JavaScript builder saves writing it by hand. Type the number you want to land on, how long the climb should take and how it should ease, and the preview counts up from your start value every time a setting changes. Add a prefix such as a currency symbol or a suffix like % or +, then copy the snippet. You can choose a CSS-only version that needs no script at all, or a few lines of JavaScript that format the number with thousands separators as it ticks.

How the animated counter CSS works

Counting numbers are the standard way to make a statistics strip feel alive: "12,500+ customers" reads as a claim, but watching it climb from zero makes the visitor look at it. The trick has traditionally needed JavaScript because CSS could not animate a number and print it as text. That changed with @property. Registering a custom property with syntax: "<integer>" tells the browser the value is a whole number rather than an opaque token, which makes it animatable in @keyframes. Feeding that property into counter-reset and printing it with counter() in a pseudo-element turns the interpolated value into visible digits, with no script and no layout thrash.

The CSS only output does exactly that. It works in every current browser, but there are two limits worth knowing. counter() prints plain integers, so there are no thousands separators and no decimals; and because the digits live in ::after, they are not part of the text content, so add an aria-label with the final figure if the number matters to screen-reader users. For a number you want formatted as 12,500 or 1.2M, switch to the JavaScript output, which reads the start, end and duration from data attributes, eases with the same curve you chose here and formats with toLocaleString, so it follows the visitor's locale.

Ease out is almost always right for a counter: it races through the early hundreds and slows into the final figure, so the eye arrives at the number as it settles. Linear looks mechanical and, for large ranges, makes the last few digits a blur until the very end. Keep the duration between one and three seconds; longer than that and people stop waiting for it.

Both versions start as soon as the element is styled or the script runs. On a real page you usually want the count to begin when the strip scrolls into view, which is a one-line IntersectionObserver that adds a class or calls the function; the scroll animation previewer linked below shows the pattern.

FAQ

Why does the CSS version show no commas?

counter() prints a plain integer. For 12,500 or 1.2M use the JavaScript output, which formats with toLocaleString in the visitor's locale, so a French visitor sees a space and a German visitor a full stop as the thousands separator.

How do I start the count when it scrolls into view?

Wrap the animation in a class and add that class from an IntersectionObserver callback, or call the JavaScript function from the same place. The scroll animation previewer generates that observer.

Can it count down?

Yes. Set the start higher than the end and it runs downwards, in both outputs. The easing applies the same way, so an ease-out countdown slows as it approaches the final value.

Which browsers support the CSS-only counter?

Every current version of Chrome, Edge, Safari and Firefox, since @property landed in Firefox 128. Older browsers show the final number straight away rather than animating, which is a safe fallback.

See also: all Shadows & Motion tools, Layout & Spacing, Shapes & SVG, UI Components, Forms & Inputs, Colour.

Related tools