spark-ui
componentbasic-number-tickersource: ok · refreshed
Basic Number Ticker
An animated number ticker component with customizable start, target, transitions, and callbacks.
npx shadcn add "https://spark-ui-olive.vercel.app/r/basic-number-ticker.json"free
Interaction
marquee
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · mediumanimationframer-motion
Files1 · source cached 10/5/2026, 11:52:15 PM
registry/spark-ui/basic-number-ticker.tsx
"use client"
import {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useState,
} from "react"
import {
animate,
AnimationPlaybackControls,
motion,
useMotionValue,
useTransform,
ValueAnimationTransition,
} from "framer-motion"
import { cn } from "@/lib/utils"
export interface NumberTickerProps {
from: number // Starting value of the animation
target: number // End value of the animation
transition?: ValueAnimationTransition // Animation configuration, refer to motion docs for more details
className?: string // additional CSS classes for styling
onStart?: () => void // Callback function when animation starts
onComplete?: () => void // Callback function when animation completes
autoStart?: boolean // Whether to start the animation automatically
}
// Ref interface to allow external control of the animation
export interface NumberTickerRef {
startAnimation: () => void
}
const NumberTicker = forwardRef<NumberTickerRef, NumberTickerProps>(
(
{
from = 0,
target = 100,
transition = {
duration: 3,
type: "tween",
ease: "easeInOut",
},
className,
onStart,
onComplete,
autoStart = true,
...props
},
ref
) => {
const count = useMotionValue(from)
const rounded = useTransform(count, (latest) => Math.round(latest))
const [controls, setControls] = useState<AnimationPlaybackControls | null>(
null
)
// Function to start the animation
const startAnimation = useCallback(() => {
if (controls) controls.stop()
onStart?.()
count.set(from)
const newControls = animate(count, target, {
...transition,
onComplete: () => {
onComplete?.()
},
})
setControls(newControls)
}, [controls, count, from, target, transition, onStart, onComplete])
// Expose the startAnimation function via ref
useImperativeHandle(ref, () => ({
startAnimation,
}))
useEffect(() => {
if (autoStart) {
startAnimation()
}
return () => controls?.stop()
}, [autoStart])
return (
<motion.span className={cn(className)} {...props}>
{rounded}
</motion.span>
)
}
)
NumberTicker.displayName = "NumberTicker"
export default NumberTicker