Countup
A countup animates a number from zero to its final value when it scrolls into view.
Overview
A countup animates a number from a starting value up to its final value. Use it for statistics on a dashboard or a landing page, where the number is the point of the block.
Write the final number as the text of the element and add data-countup. Tabler finds every such element and animates it when it scrolls into view, so no JavaScript of your own is needed.
30000
<h1 data-countup="true">30000</h1>Installation
Install countup.js with npm:
npm install countup.jsyarn add countup.jspnpm install countup.jsbun install countup.jsOr include it from a CDN:
<script src="https://cdn.jsdelivr.net/npm/@tabler/[email protected]/dist/libs/countup.js/dist/countUp.umd.js"></script>
Tabler reads the library from the countUp global, so load the UMD build above before tabler.js. If you install with npm and bundle it yourself, assign it first:
import * as countUp from 'countup.js';
window.countUp = countUp;
For options beyond the ones below, see the countUp.js website.
Usage
Add data-countup to any text element and write the target number inside it. The animation starts as soon as the number enters the viewport.
<h1 data-countup>30000</h1>
Pass options as JSON in the same attribute. Every option below works this way.
<h1 data-countup='{"duration":4,"suffix":"%"}'>300</h1>
Duration
duration sets how long the animation takes, in seconds. The default is 2.
30000
30000
30000
<h1 data-countup="true">30000</h1>
<h1 data-countup="{"duration":4}">30000</h1>
<h1 data-countup="{"duration":6}">30000</h1>Starting value
startVal sets the starting value; the default is zero. A start value above the final one makes the number count down instead of up.
30000
30000
<h1 data-countup="{"startVal":12345}">30000</h1>
<h1 data-countup="{"startVal":47655}">30000</h1>Decimal places
decimalPlaces sets how many decimals are shown. The default is 0.
3.123
3.123
3.123
3.123
<h1 data-countup="true">3.123</h1>
<h1 data-countup="{"decimalPlaces":1}">3.123</h1>
<h1 data-countup="{"decimalPlaces":2}">3.123</h1>
<h1 data-countup="{"decimalPlaces":3}">3.123</h1>Easing
Easing is on by default, so the animation slows down towards the end. Set "useEasing": false for a linear animation.
30000
30000
<h1 data-countup="true">30000</h1>
<h1 data-countup="{"useEasing": false}">30000</h1>Use grouping
Grouping is on by default, so thousands get a separator. Set "useGrouping": false to turn it off.
30000
30000
<h1 data-countup="true">30000</h1>
<h1 data-countup="{"useGrouping": false}">30000</h1>Separator
separator sets the thousands separator.
3000000
3000000
<h1 data-countup="true">3000000</h1>
<h1 data-countup="{"separator":" "}">3000000</h1>Decimal separator
decimal sets the decimal separator.
3.12
3.12
<h1 data-countup="{"decimalPlaces":2}">3.12</h1>
<h1 data-countup="{"decimalPlaces":2,"decimal":","}">3.12</h1>Prefix
prefix adds text before the number, for example a currency symbol.
30000
30000
<h1 data-countup="{"prefix":"$"}">30000</h1>
<h1 data-countup="{"prefix":"€"}">30000</h1>Suffix
suffix adds text after the number, for example a percent sign.
300
300
<h1 data-countup="{"suffix":"%"}">300</h1>
<h1 data-countup="{"suffix":"‰"}">300</h1>JavaScript
Tabler automatically initializes all elements with data-countup on page load. This is the code that runs:
const countupElements: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>('[data-countup]')
if (countupElements.length) {
countupElements.forEach(function (element: HTMLElement) {
let options: Record<string, any> = {}
try {
const dataOptions = element.getAttribute('data-countup') ? JSON.parse(element.getAttribute('data-countup')!) : {}
options = Object.assign(
{
enableScrollSpy: true,
},
dataOptions,
)
} catch (error) {
// ignore invalid JSON
}
// Strip thousands separators, currency symbols and other non-numeric characters
// so formatted targets like "1,234", "1 234" or "$99.5" parse correctly.
const value = parseFloat((element.textContent ?? '').replace(/[^0-9.-]/g, ''))
if (!Number.isNaN(value) && window.countUp && window.countUp.CountUp) {
const countUp = new window.countUp.CountUp(element, value, options)
// When scrollSpy is enabled CountUp starts the animation itself once the
// element scrolls into view, so only start manually when it's disabled.
if (!countUp.error && !options.enableScrollSpy) {
countUp.start()
}
}
})
}Accessibility
- Write the final number in the HTML, as the examples do. That is what a search engine indexes, what shows without JavaScript, and what a screen reader reads if it reaches the element before the animation starts.
- Never put a countup inside an
aria-liveregion. The text changes on every frame, so a live region would announce dozens of intermediate numbers. - The number on its own says nothing. Give it a visible label next to it, or an
aria-labelon the block that holds both. - Tabler does not check
prefers-reduced-motion, so a countup animates even for users who asked for less motion. Skip the initialization for them:
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
document.querySelectorAll('[data-countup]').forEach((el) => el.removeAttribute('data-countup'));
}
Run it before tabler.js, so the number simply stays at its final value.
