Playground
Counts a number to its value the first time it is on screen: for the statistics on a landing page, a total that just changed, anything that should arrive rather than appear. It takes its font, size and colour from where you put it.
Put the server-formatted number inside it. That is what search engines and people without JavaScript see, and screen readers always hear the final value rather than the frames of the count.
Examples
On scroll
<p style="font-size: 1.25rem">
<sb-count-up value="1113" style="font-size: 2.5rem; font-weight: 700">1113</sb-count-up>
models, <sb-count-up value="56">56</sb-count-up> makes.
</p>
Decimals and units
decimals is kept for every frame, so the width does not change while it counts. Units stay outside the element.
CHF
<p style="font-size: 2rem">
CHF <sb-count-up value="0.52" decimals="2" duration="2000">0.52</sb-count-up> / km
</p>
A new value
When the server sends a new value while the number is on screen, it counts from where it stands.
<div data-signals="{_total: 1200}" style="display: flex; gap: 1rem; align-items: center">
<sb-count-up data-attr:value="$_total" data-preserve-attr="value" style="font-size: 2rem">1200</sb-count-up>
<sb-button data-on:click="$_total += Math.round(Math.random() * 500)">Add an order</sb-button>
</div>
Accessibility
Screen readers get the final value as text; the counting digits are hidden from them. With prefers-reduced-motion: reduce the number is shown at its value straight away, and a new value replaces it without counting.
Installation
Add Datastar with Rocket and the Starbase autoloader once per page, then use the tag. The autoloader imports each component the first time its tag appears, including tags added later by a Datastar morph.
<!-- Once per page: Datastar with Rocket, and the Starbase autoloader.
It loads every <sb-…> component the first time its tag appears. -->
<script type="importmap">
{ "imports": { "datastar": "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js" } }
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/autoloader.js"></script>
<!-- Optional, no flash of undefined elements: class="sb-cloak" on <html>, and -->
<style>.sb-cloak :not(:defined) { visibility: hidden }</style>
<sb-count-up value="4681" style="font-size: 2rem">4681</sb-count-up>
<!-- In production, pin today's catalog instead of the latest: the browser then
refuses any file that changed. Add "integrity" to the import map above: the
hashes from https://starbase.zweiundeins.gmbh/c/@722e189276ac/importmap.json and Datastar's, below. -->
<!--
<script type="importmap">
{ "imports": { "datastar": "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js" },
"integrity": { "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js": "sha384-vUxZojLrF1Ar3de5h7VINqhJXBgjyZS4U49pHvGa87kum6j5Xn6JRziuARNw5ELG", "…": "…from importmap.json" } }
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/@722e189276ac/autoloader.js" integrity="sha384-scFa7/4jztDOx1YZT9/1g1ULYm9LsDlQn/8hMTyyD4IkPnZV2pfhHytoNTWTJG6b"></script>
-->
<!-- Or load just this component, pinned to this version. The minified module
is what the autoloader uses; the readable source is the same URL without .min. -->
<!-- <script type="module" src="https://starbase.zweiundeins.gmbh/c/count-up@2aadfff22690/count-up.min.js" integrity="sha384-yjJQfnl3zFj2W7ghTEmUGV5VaL4Iah+86kkTayK2Cp5ctiULI4XzbJXLpGY5hJ2Y"></script> -->Size
Each file compressed on its own, the way it is served (gzip -9, brotli -11). The autoloader loads the minified files (esbuild), so that last column is what a page downloads; the readable source is always there too. Datastar and Rocket are shared by every component and not counted.
| File | Original | gzip | brotli | minified |
|---|---|---|---|---|
count-up.js | 3.7 kB | 1.7 kB | 1.4 kB | 967 B |
API reference
Props
| Attribute | Type | Default | Description |
|---|---|---|---|
value | number | 0 | The number to arrive at. |
from | number | 0 | Where the count starts the first time it is seen. |
decimals | number | 0 | Fixed digits after the decimal mark, kept for every frame so the width stays put. |
duration | number | 1400 | Length of the count in milliseconds. |
grouping | boolean | true | Group thousands the way the locale does. |
lang | string | "" | Locale for the separators (default: the page's lang, then the browser's). |