{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "number-ticker",
  "title": "Number Ticker",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "Counts up to a number when it scrolls into view, with locale-aware formatting for currency, percentages and decimals.",
  "dependencies": [
    "motion@^12"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/number-ticker.tsx",
      "content": "// Ballmac UI: Number Ticker. https://ui.ballmac.com/components/number-ticker\n\"use client\"\n\nimport * as React from \"react\"\nimport { animate, useInView, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype NumberTickerProps = Omit<React.ComponentProps<\"span\">, \"children\"> & {\n  /** The number to count to. */\n  value: number\n  /** Where the count starts. Defaults to 0. */\n  from?: number\n  /** Seconds. Defaults to 1.4. */\n  duration?: number\n  /** Seconds to wait after the number scrolls into view. */\n  delay?: number\n  /** Intl.NumberFormat options, for example { style: \"currency\", currency: \"USD\" }. */\n  format?: Intl.NumberFormatOptions\n  /** BCP 47 locale. Fixed (not the browser's) so server and client render the same text. */\n  locale?: string\n}\n\nfunction NumberTicker({\n  value,\n  from = 0,\n  duration = 1.4,\n  delay = 0,\n  format,\n  locale = \"en-US\",\n  className,\n  ...props\n}: NumberTickerProps) {\n  const ref = React.useRef<HTMLSpanElement>(null)\n  const inView = useInView(ref, { once: true, margin: \"0px 0px -10% 0px\" })\n  const reduceMotion = useReducedMotion()\n  const formatter = React.useMemo(() => new Intl.NumberFormat(locale, format), [locale, format])\n\n  React.useEffect(() => {\n    const node = ref.current\n    if (!node) return\n    if (reduceMotion) {\n      node.textContent = formatter.format(value)\n      return\n    }\n    node.textContent = formatter.format(from)\n    if (!inView) return\n    const controls = animate(from, value, {\n      duration,\n      delay,\n      ease: [0.22, 1, 0.36, 1],\n      onUpdate: (latest) => {\n        const decimals = format?.maximumFractionDigits ?? (Number.isInteger(value) ? 0 : 2)\n        node.textContent = formatter.format(Number(latest.toFixed(decimals)))\n      },\n    })\n    return () => controls.stop()\n  }, [inView, reduceMotion, value, from, duration, delay, formatter, format?.maximumFractionDigits])\n\n  return (\n    <span\n      ref={ref}\n      data-slot=\"number-ticker\"\n      className={cn(\"inline-block tabular-nums tracking-tight\", className)}\n      {...props}\n    >\n      {formatter.format(reduceMotion ? value : from)}\n    </span>\n  )\n}\n\nexport { NumberTicker, type NumberTickerProps }\n",
      "type": "registry:ui",
      "target": "@components/ballmac/number-ticker.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "animation",
      "counter",
      "stats",
      "motion"
    ],
    "version": "1.0.1",
    "updated": "2026-09-28",
    "ai": {
      "summary": "Animated number for stats and KPIs. Respects reduced motion by showing the final value immediately.",
      "whenToUse": [
        "Stat sections on landing pages",
        "Dashboard KPIs that load in"
      ],
      "whenNotToUse": [
        "Numbers that update every second (animate a live value instead)",
        "Prices users need to read instantly"
      ],
      "composesWith": [
        "stat-card"
      ],
      "a11y": [],
      "customization": [
        "format accepts any Intl.NumberFormat options",
        "locale defaults to en-US so server and browser render identical text; pass your locale explicitly",
        "duration and delay in seconds"
      ]
    },
    "examples": [
      "number-ticker-demo",
      "number-ticker-currency"
    ],
    "url": "https://ui.ballmac.com/components/number-ticker"
  },
  "categories": [
    "motion"
  ],
  "type": "registry:ui"
}