Animate Digits
Per-digit blur-slide animation with direction awareness — only the digits that change animate, sliding up when increasing and down when decreasing.
Installation
CLI installation is not available for Pro components. I'm on it.
File Structure
Usage
import { AnimateDigits } from "@/components/unlumen-ui/animate-digits";
export default function Example() {
return <AnimateDigits value="05:00" className="text-6xl font-bold" />;
}API Reference
valuestring—The string to display. Each digit that changes between renders animates independently. Non-digit characters (e.g. `:`, `.`) are rendered statically.
gap?number2Spacing in pixels between characters.
className?string—Additional classes applied to the outer flex container.
digitClassName?string—Additional classes applied to each character wrapper.
enterStiffness?number150Spring stiffness for the entering digit.
enterDamping?number10Spring damping for the entering digit.
exitStiffness?number150Spring stiffness for the exiting digit.
exitDamping?number15Spring damping for the exiting digit.
direction?"dynamic" | "up" | "down""dynamic"`dynamic` infers direction from the digit value (up if increasing, down if decreasing). `up` and `down` force a fixed direction regardless of the value.
enterY?number32Vertical offset in pixels from which the entering digit slides in.
enterBlur?number52Blur amount in pixels applied to the entering digit at the start of its animation.
enterScale?number0.7Scale applied to the entering digit at the start of its animation.
Notes
- Only digits (
0–9) animate. Separator characters like:,., or/are rendered as plain<span>elements and never trigger an animation. - Direction is inferred per-digit when
direction="dynamic": a digit going from3to7slides up;7to3slides down. Force a fixed direction with"up"or"down". - Each
DigitCellmaintains an exit queue (capped at 3 items) so rapid changes stack their outgoing digits instead of replacing them. - The enter animation uses Motion
useSpring— the digit snaps to its off-screen position viajump()(y, opacity, scale, blur) then springs to its resting values. The exit uses amotion.spanwith a standardanimatetransition.
Known limitation — rapid-fire interruptions
When a new value arrives while an enter animation is already in flight, the current implementation calls jump() again, which teleports the spring to the off-screen start position and resets its velocity. This causes a visible cut rather than a smooth continuation.
The ideal behavior would be: on interruption, spring from the current position and velocity toward the target — no cut, no hard reset.
What was attempted: replacing useSpring + jump() with useMotionValue + Motion's imperative animate(), which accepts a from parameter and can be interrupted without resetting velocity. The approach was sound in theory, but distinguishing "first enter" (needs from off-screen) from "interrupted enter" (needs to continue from current position) proved unreliable in practice:
- A boolean flag (
isAnimatingRef) was tried, butanimate().then()never resolves when the animation is interrupted mid-flight, so the flag stayedtruepermanently after the first interruption — breaking all subsequent enter animations. - Reading the live motion values at trigger time (
y.get() < 0.5, etc.) was tried as a proxy for "is the digit at rest?", but the threshold logic was fragile and produced inconsistent results across different spring configurations.
What would make it work: Motion's animate() imperative API does preserve velocity across interruptions when called without from. The missing piece is a reliable way to know whether the digit is entering for the first time (so from should be set to the off-screen position) versus being interrupted mid-animation (so no from should be passed). A clean solution could use useAnimationFrame to poll velocity, or hook into Motion's internal animation state — neither of which has a stable public API today.
Credits
Built by leo.
Keep in mind
Most components on this site are inspired by or recreated from existing work across the web. I'm not here to take credit; just to learn, experiment, and sometimes push things a bit further. If something looks familiar and I forgot to mention you, reach out and I'll fix that right away.
This component is part of Unlumen UI [ Pro ]
Includes advanced motion, patterns, and premium support.