Stat counter
Each digit rolls to its new value, odometer style, then catches the light.
<facet-number value="12480" motion="roll" glint></facet-number>
Ten faceted, gem-cut digits as a drop-in web component. Change the value and the number animates: seven motions, six finishes, reduced-motion aware, no dependencies. Try it below, then copy a use case.
npm i facet-numeralsEvery demo below is the real <facet-number> element from the npm package. Copy the snippet under it.
Each digit rolls to its new value, odometer style, then catches the light.
<facet-number value="12480" motion="roll" glint></facet-number>
Split-flap flips, padded to six digits so the width never changes.
<facet-number id="clock" pad="6" motion="flip"></facet-number>
setInterval(() => {
const d = new Date();
clock.value = [d.getHours(), d.getMinutes(), d.getSeconds()]
.map(n => String(n).padStart(2, '0')).join('');
}, 1000);The new digit drops in and settles on the baseline. Gold for first-place moments.
<facet-number value="3" motion="drop" finish="gold" glint></facet-number>
A quick pop on single increments. Only the digits that change move.
<facet-number id="likes" value="128" motion="pop"></facet-number> likeButton.onclick = () => likes.set(+likes.value + 1);
Order
The default height is 1.4em, so it sits in a sentence. Pearl is the finish for light backgrounds.
Order <facet-number value="4271" finish="pearl"></facet-number> is ready
No JavaScript in the browser: generate the SVG markup in Node and send it with the page.
import { numberSVG } from 'facet-numerals';
const html = numberSVG(2026, { finish: 'graphite' }).join('');Every study stays under 800 ms, eases out, and lands on the baseline without drift. Select a card to replay it with a new value. The playback speed above applies here too.
Set finish on the element. The metal and pearl finishes map each facet's brightness through a colour ramp, so the light still falls from the top left. Select a tile to catch the light.
Works in plain HTML, React, Vue and Svelte. TypeScript types are included.
npm i facet-numerals
No build step? Load it from a CDN instead:
<script type="module" src="https://cdn.jsdelivr.net/npm/facet-numerals/src/define.js"></script>
import 'facet-numerals/define';
Or call define('my-number') from the main entry to use your own tag name.
<facet-number value="2026" motion="roll" glint></facet-number>
Change value, as an attribute or a property, and the number animates to it.
| Attribute | Values | Default |
|---|---|---|
| value | Any number. Non-digits are ignored. | 0 |
| motion | roll · flip · drop · focus · pop · assemble · draw · fade · none | roll |
| finish | gem · gold · graphite · pearl · wire · glass | gem |
| glint | Present: a highlight sweeps across after each change | off |
| pad | Minimum number of digits, padded with zeros | 0 |
| speed | Playback rate, for example 0.25 to slow motions down | 1 |
| JavaScript and CSS | What it does |
|---|---|
| await el.set(4815, { motion: 'flip', glint: true }) | Animates to a value and resolves when every digit has settled |
| await el.glint() | Sweeps a highlight across the number |
| facet-settle | Event fired after each change, with detail.value |
| digitSVG(7) · numberSVG(2026) | SVG strings for server rendering or static use; IDs are unique per call |
| --facet-height | Size (default 1.4em). Each digit is 0.7 times as wide as it is tall. Also --facet-gap and ::part(number) |
| facet-numerals/svg/0.svg … 9.svg | The digits as plain SVG files, on a 56 × 80 tabular cell |
Screen readers hear the number: the element sets role="img" and an aria-label with the current value. With reduced motion turned on, every change becomes a short crossfade.
Durations are at 1× speed. Stagger runs left to right across digit places, so the eye reads the number the same way it reads text.
| Motion | Enter | Exit | Easing | Stagger | Use for |
|---|
:root {
/* Facet Numerals · motion tokens */
--num-ease-out: cubic-bezier(.16, 1, .3, 1); /* roll, focus, settle */
--num-ease-flip: cubic-bezier(.2, .7, .3, 1); /* flip, drop */
--num-ease-in: cubic-bezier(.5, 0, .75, 0); /* every exit */
--num-ease-glint: cubic-bezier(.45, 0, .2, 1); /* light sweep */
--num-dur-exit: 170ms;
--num-dur-focus: 320ms;
--num-dur-pop: 360ms;
--num-dur-flip: 420ms;
--num-dur-drop: 460ms;
--num-dur-glint: 700ms;
--num-dur-roll: 380ms; /* + 45ms per digit travelled */
--num-stagger: 40ms; /* per place, left to right */
--num-cell: 56 / 80; /* tabular advance, cap 64, baseline 72 */
}
MIT licensed, code and artwork. Source on GitHub, package on npm.