Number Ticker
Number Ticker component with smooth animations and modern UI.
Installation
Add this component to your project using the CLI:
terminal
npx vui-registry-cli-v1 add number-tickerSource Code
number-ticker.tsx
'use client'
import { useEffect, useRef, useState } from 'react'
import { useInView, useMotionValue, useSpring, useVelocity, useTransform, motion } from 'framer-motion'
import { cn } from '@/lib/utils'
export default function NumberTickerPreview() {
const [value, setValue] = useState(100)
return (
<div className="min-h-[400px] w-full flex flex-col items-center justify-center bg-neutral-100 dark:bg-neutral-950 p-8 space-y-12 font-sans">
<div className="text-center space-y-4">
<h2 className="text-6xl font-bold tracking-tighter text-neutral-800 dark:text-neutral-100 tabular-nums">
$<NumberTicker value={value} />
</h2>
<p className="text-neutral-500 uppercase tracking-widest text-xs font-medium">Total Revenue</p>
</div>
<div className="flex gap-4">
<button
onClick={() => setValue(v => v + Math.floor(Math.random() * 1000) + 100)}
className="px-6 py-2.5 bg-neutral-900 dark:bg-white text-white dark:text-black rounded-full text-sm font-medium hover:scale-105 active:scale-95 transition-all shadow-lg"
>
Add Random
</button>
<button
onClick={() => setValue(v => Math.max(0, v - 500))}
className="px-6 py-2.5 bg-neutral-200 dark:bg-neutral-800 text-neutral-900 dark:text-white rounded-full text-sm font-medium hover:bg-neutral-300 dark:hover:bg-neutral-700 transition-colors"
>
Subtract $500
</button>
</div>
<div className="grid grid-cols-3 gap-12 text-center pt-8 border-t border-neutral-200 dark:border-neutral-800 w-full max-w-2xl">
<div>
<p className="text-xs font-medium text-neutral-400 uppercase tracking-wider mb-2">Active Users</p>
<p className="text-3xl font-bold font-mono tracking-tight">
<NumberTicker value={8432} />
</p>
</div>
<div>
<p className="text-xs font-medium text-neutral-400 uppercase tracking-wider mb-2">Downloads</p>
<p className="text-3xl font-bold font-mono tracking-tight text-indigo-500">
<NumberTicker value={1205} />k
</p>
</div>
<div>
<p className="text-xs font-medium text-neutral-400 uppercase tracking-wider mb-2">Satisfaction</p>
<p className="text-3xl font-bold font-mono tracking-tight text-emerald-500">
<NumberTicker value={98} />%
</p>
</div>
</div>
</div>
)
}
function NumberTicker({
value,
direction = 'up',
delay = 0,
className,
}: {
value: number
direction?: 'up' | 'down'
className?: string
delay?: number // delay in s
}) {
const ref = useRef<HTMLSpanElement>(null)
const motionValue = useMotionValue(direction === 'down' ? value : 0)
const springValue = useSpring(motionValue, {
damping: 60,
stiffness: 100,
})
const isInView = useInView(ref, { once: true, margin: '0px' })
useEffect(() => {
if (isInView) {
setTimeout(() => {
motionValue.set(direction === 'down' ? 0 : value)
}, delay * 1000)
}
}, [motionValue, isInView, delay, value, direction])
useEffect(() => {
const unsubscribe = springValue.on('change', (latest) => {
if (ref.current) {
ref.current.textContent = Intl.NumberFormat('en-US').format(
Number(latest.toFixed(0)),
)
}
})
return unsubscribe
}, [springValue])
return (
<span
className={cn(
'inline-block tabular-nums text-black dark:text-white tracking-wider',
className,
)}
ref={ref}
/>
)
}

