← Back to gallery
CSS

Number Counter Odometer Transition

A metric transition pattern that rolls fixed-width digit columns with translateY and tabular numerals so changing numbers feel continuous without resizing the row.

odometernumber-countertranslateYtabular-numsaria-livefixed-widthprefers-reduced-motion

Data-viz / number state / odometer

Number Counter Odometer Transition

Three number transitions roll digit columns with transform so metric changes feel continuous without reflowing the label row.

Whole value · translateY

Single Strip

Translate one stacked value strip inside a fixed metric box.

  • value strip
  • translateY
  • fixed box

Staggered columns

Staggered Columns

Stagger digit columns while preserving total metric width.

  • stagger
  • fixed width
  • metric

Rollover carry · window

Carry Window

Give the carry its own window instead of widening the text row.

  • carry
  • rollover
  • fixed slot

Odometer inspector

Single Strip

  • value strip
  • translateY
  • fixed box

The whole value rolls as one clipped strip when the number changes as a compact unit.

Helped you ship something? 🐟 Send my cat a churu

/* Single Strip: Translate one stacked value strip inside a fixed metric box. */
.motion-odometer-strip {
  --motion-duration: 2.8s;
  display: inline-grid;
  font-variant-numeric: tabular-nums;
}

.motion-odometer-strip__window {
  width: 4ch;
  height: 1.35em;
  overflow: hidden;
}

.motion-odometer-strip__values b {
  display: grid;
  place-items: center;
  height: 1.35em;
  animation: odometer-strip-roll var(--motion-duration) cubic-bezier(.45, 0, .2, 1) infinite;
}

@keyframes odometer-strip-roll {
  0%, 28% { transform: translateY(0); }
  58%, 76% { transform: translateY(-1.35em); }
  100% { transform: translateY(-2.7em); }
}

@media (prefers-reduced-motion: reduce) {
  .motion-odometer-strip__values b { animation: none; }
}

How to make this

A CSS odometer counter rolls fixed-width digit strips with translateY and tabular numerals so changing numbers never resize the metric row.

html
1<p class="metric" aria-live="polite">  <span class="metric__label">Revenue</span>  <span class="metric__digits" aria-hidden="true">    <span class="metric__slot"><span>3</span><span>3</span><span>3</span></span>    <span class="metric__slot"><span>4</span><span>4</span><span>4</span></span>    <span class="metric__slot"><span>5</span><span>6</span><span>7</span></span>  </span>  <span class="sr-only">Revenue changed to 347</span></p> <style>.metric {  display: inline-flex;  align-items: center;  gap: .75rem;16  font-variant-numeric: tabular-nums;}.metric__digits {  display: inline-flex;  gap: .15rem;}.metric__slot {23  width: 1ch;  height: 1.4em;25  overflow: hidden;  display: grid;}.metric__slot > span {  height: 1.4em;  display: grid;  place-items: center;  animation: digit-roll 2.8s cubic-bezier(.45, 0, .2, 1) infinite;}@keyframes digit-roll {  0%, 30% { transform: translateY(0); }  60%, 100% { transform: translateY(-2.8em); }}.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }39@media (prefers-reduced-motion: reduce) {  .metric__slot > span { animation: none; }}</style>

Annotated snippet

  1. Line 1Announce the final metric change politely. Do not announce every rolling intermediate digit.
    PitfallShould screen readers hear each digit during the roll?

    No. Hide decorative digit strips and announce the committed final value with a polite live region or visible text.

  2. Line 16Tabular numerals keep every digit the same width, which prevents label jitter.
    PitfallWhy use tabular numerals?

    Proportional numerals have different widths, so a rolling counter can jitter even when the slot is fixed.

  3. Line 23Each slot reserves one digit of width. The strip moves inside the slot instead of resizing the row.

    Swapping the whole number hides which digit changed. Fixed digit slots make 345 → 346 → 347 read as one controlled roll.

    PitfallHow do I stop nearby UI from moving?

    Reserve fixed digit slots and animate transform inside them. Do not animate raw text width.

  4. Line 25The slot clips the digit strip so the roll does not leak into adjacent content.

    A digit strip is only readable when the slot clips it. Without the window, 5, 6, and 7 leak into the same frame.

    PitfallWhy does the digit strip need overflow hidden?

    The strip contains multiple numbers stacked vertically. The slot clips it to one visible digit at a time.

  5. Line 39Reduced motion snaps to a stable number while preserving the fixed metric layout.
    PitfallWhat should reduced motion do?

    Show the final number immediately while keeping the same fixed slots and accessible announcement.

Other pitfalls

What should I verify before shipping this pattern?
Check that the preview card and showcase communicate the same start and end state, every inspector control visibly changes the animation, compare demos stay fixed-height and centered, and reduced motion preserves the information without running a substitute loop.

Notes

Overview

An odometer counter rolls digits inside fixed slots. The animation is useful because it preserves metric width while making the value change perceptible. The reusable pattern is not a KPI card; it is the fixed-slot digit transition that prevents nearby labels, icons, or cards from shifting.

When to use it

Use it for KPIs, counters, balances, score changes, and short metrics where the number is the subject and the update deserves attention. It works best when values change occasionally and the user benefits from seeing direction. Do not use it for long tables, rapidly updating telemetry, or logs where users need immediate legibility over motion.

How it works

Each digit column is an overflow: hidden slot. A vertical strip of digits moves with translateY, font-variant-numeric: tabular-nums keeps widths consistent, and the wrapper reserves the total metric footprint.

Production gotchas

Never animate the text node width directly. That causes neighboring labels or cards to move, and the effect becomes more obvious when the new value has more digits. Also avoid announcing every intermediate digit; assistive tech should receive the final committed value, not the decorative roll.

Accessibility

Use aria-live politely for the committed value and hide decorative rolling strips if a separate accessible number is present. Reduced motion should snap to the final value while keeping the fixed slots and visible changed state. Do not make color or motion the only sign that a number increased or decreased.

References

Implementation depth

An odometer counter is a fixed-slot transition, not a text-width animation. Each digit column reserves the width of the largest numeral and rolls a transform strip, so changing values do not resize the metric row.

Use tabular numbers and announce the final value once. Screen readers should not hear every intermediate digit in a roll. Reduced motion can snap to the new number while preserving the fixed slots and visible change state.

The pattern should be abstracted away from KPI cards. A KPI may use it, but the slug is about fixed digit columns, carry timing, overflow clipping, and stable metric width.

Watch sign, decimal, and unit changes separately. Digits can roll, but currency symbols, percent signs, separators, and labels should usually remain stable or crossfade without shifting the row.