Counter animation
Animate numbers without JavaScript using a digit “roller”: a vertical strip translated with steps() so digits snap crisply.
Requests
Rolling counter (CSS only)
Quick implementation
/* One digit “window” */
.digit {
--digit-h: 2.25rem;
height: var(--digit-h);
overflow: hidden;
}
/* Vertical strip of 0..9 */
.strip {
display: grid;
grid-auto-rows: var(--digit-h);
animation: roll 2.5s steps(10) infinite;
}
@keyframes roll {
to { transform: translateY(calc(var(--digit-h) * -10)); }
}
@media (prefers-reduced-motion: reduce) {
.strip { animation: none; }
}
Prompt this to your LLM
Includes role, constraints, two framework variants, and edge cases to handle.
You are a senior frontend engineer building a CSS-only animated counter.
Goal: Animate a numeric counter using a digit roller effect (0-9) with crisp snapping, no JavaScript.
Technical constraints:
- Each digit uses a fixed-height window and an inner vertical strip of digits 0..9 (optionally repeat 0 at the end).
- Animate the strip with transform: translateY(...) and steps(10) so the digits snap instead of easing between values.
- Use a CSS custom property (e.g. --digit-h) for digit height so sizing scales cleanly.
- Stagger different digits using different animation-duration values (or animation-delay) to make it feel dynamic.
- Respect prefers-reduced-motion: reduce by disabling the animation.
- Use oklch() for explicit colors and site tokens (var(--card), var(--muted), etc.) where appropriate.
Framework variant (pick one):
A) Vanilla HTML/CSS with 4 digits and an optional suffix.
B) React component <RollingCounter digits={4} /> that renders the strips.
Edge cases to handle:
- Layout shift: ensure the digit window has a fixed width and height.
- Font rendering: use a stable font and align digits center.
- Screen readers: provide an aria-label with a static value if needed.
Return HTML + CSS.
Why this matters in 2026
Not every counter needs JavaScript. For decorative, “alive” UI (loading stats, dashboard ambience), a CSS roller delivers the right feel with almost zero complexity: it’s just a transformed strip and steps().
The logic
Instead of trying to animate text content, you render all digits in a column and move the column. steps(10) converts the animation into 10 discrete jumps, so each digit snaps cleanly and never blurs between values.
Accessibility & performance
Because this is motion, always include a prefers-reduced-motion fallback. Performance-wise, animating transform is compositor-friendly. If the counter conveys important information, render the real value in text and treat the animation as decoration.