Number Ticker
ui-beats/number-tickerFreeComponent
The NumberTicker component counts a number up when it scrolls into view, with locale-aware formatting.
Install it
npx shadcn@latest add https://uibeats.com/r/number-ticker.jsonSource
1"use client";23import {4 useInView,5 useMotionValueEvent,6 useSpring,7 useReducedMotion,8} from "motion/react";9import { useEffect, useRef } from "react";1011interface NumberTickerProps {12 /** The value to count to. */13 value: number;14 /** Where the count starts. */15 from?: number;16 decimals?: number;17 prefix?: string;18 suffix?: string;19 /** Count only the first time it scrolls into view. */20 once?: boolean;21 /** BCP 47 locale for grouping separators. */22 locale?: string;23 className?: string;24}2526/**27 * A number that counts up when it scrolls into view.28 *29 * The animated digits are written straight to the DOM node rather than held30 * in state: a spring emits a value every frame, and re-rendering React sixty31 * times a second to print a number is wasted work.32 */33export function NumberTicker({34 value,35 from = 0,36 decimals = 0,37 prefix = "",38 suffix = "",39 once = true,40 locale = "en-US",41 className = "",42}: NumberTickerProps) {43 const ref = useRef<HTMLSpanElement>(null);44 const isInView = useInView(ref, { once, margin: "0px 0px -20% 0px" });45 const prefersReducedMotion = useReducedMotion();4647 const spring = useSpring(from, {48 damping: 40,49 stiffness: 90,50 restDelta: decimals > 0 ? 0.001 : 0.5,51 });5253 const format = (input: number) =>54 `${prefix}${new Intl.NumberFormat(locale, {55 minimumFractionDigits: decimals,56 maximumFractionDigits: decimals,57 }).format(input)}${suffix}`;5859 useEffect(() => {60 if (!isInView) return;61 if (prefersReducedMotion) {62 spring.jump(value);63 return;64 }65 spring.set(value);66 }, [isInView, prefersReducedMotion, spring, value]);6768 useMotionValueEvent(spring, "change", (latest) => {69 const node = ref.current;70 if (!node) return;71 // firstChild is the visible span; see the markup below.72 const visible = node.firstChild as HTMLElement | null;73 if (visible) visible.textContent = format(latest);74 });7576 return (77 <span ref={ref} className={className}>78 {/* Only the settled value is announced. Without this, a screen reader79 would read out every intermediate number as the spring runs. */}80 <span aria-hidden="true" className="tabular-nums">81 {format(from)}82 </span>83 <span className="sr-only">{format(value)}</span>84 </span>85 );86}8788export default NumberTicker;
What it pulls in
npm packages
Files it writes
More from ui-beats
All 34 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Animated Beamanimated-beam | ui-beats | Components | Free | 1 dep | |
| Animated Listanimated-list | ui-beats | Components | Free | 1 dep | |
| Border Beamborder-beam | ui-beats | Components | Free | 1 dep | |
| Bouncebounce | ui-beats | Components | Free | 1 dep | |
| Card Stackcard-stack | ui-beats | Components | Free | 1 dep | |
| Dockdock | ui-beats | Components | Free | 1 dep | |
| Fade Infade-in | ui-beats | Components | Free | 1 dep | |
| Fade In Unblurfade-in-unblur | ui-beats | Components | Free | 1 dep |
