SVG digits · web component · MIT

Facet Numerals

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-numerals
56 × 80 cell · cap 64 · baseline 721× speed
Up to 6 digits
Motion
Finish
Playback
Odometer roll
01 · Use cases

Drop it in wherever a number changes

Every demo below is the real <facet-number> element from the npm package. Copy the snippet under it.

Downloads this week

Stat counter

Each digit rolls to its new value, odometer style, then catches the light.

<facet-number value="12480" motion="roll" glint></facet-number>

Live clock

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);
Leaderboard rank

Rank or score

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>

Like button

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 is ready

Inline with text

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

Static SVG, rendered on the server

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('');
02 · Motions

Eight motions, each for a specific job

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.

03 · Finishes

One geometry, six finishes

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.

04 · Get started

Install, register, set a value

Works in plain HTML, React, Vue and Svelte. TypeScript types are included.

1. Install

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>

2. Register the element

import 'facet-numerals/define';

Or call define('my-number') from the main entry to use your own tag name.

3. Use it

<facet-number value="2026" motion="roll" glint></facet-number>

Change value, as an attribute or a property, and the number animates to it.

AttributeValuesDefault
valueAny number. Non-digits are ignored.0
motionroll · flip · drop · focus · pop · assemble · draw · fade · noneroll
finishgem · gold · graphite · pearl · wire · glassgem
glintPresent: a highlight sweeps across after each changeoff
padMinimum number of digits, padded with zeros0
speedPlayback rate, for example 0.25 to slow motions down1
JavaScript and CSSWhat 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-settleEvent 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-heightSize (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.svgThe 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.

05 · Motion tokens

Timing and easing tokens

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.

MotionEnterExitEasingStaggerUse 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.