Live Counter
Animated numeric display for live metrics. Pass `value` and optional `previousValue`; trend is derived when `showTrend` is enabled.
Default
Compact KPI card with formatted value and up trend.
128.3K
Previous: 128.3K | Current: 128.3K
default-example.tsx
1"use client";23import { useEffect, useState } from "react";4import { LiveCounter, formatCompact } from "@/registry/ui";5import { Card, CardContent } from "@/components/ui/card";67const useLiveDemoCounter = (initialValue = 128_340) => {8 const [counterState, setCounterState] = useState({9 value: initialValue,10 previousValue: initialValue,11 });1213 useEffect(() => {14 const id = setInterval(() => {15 setCounterState((prev) => {16 const delta = Math.round((Math.random() - 0.35) * 2200);17 return {18 previousValue: prev.value,19 value: Math.max(95_000, prev.value + delta),20 };21 });22 }, 1500);2324 return () => clearInterval(id);25 }, []);2627 return counterState;28}2930export function LiveCounterDefaultExample() {31 const { value, previousValue } = useLiveDemoCounter();3233 return (34 <Card>35 <CardContent className="space-y-4">36 <LiveCounter37 value={value}38 previousValue={previousValue}39 label="Active Users"40 formatValue={formatCompact}41 suffix="+"42 showTrend43 size="xl"44 />4546 <p className="font-mono text-xs text-muted-foreground">47 Previous: {formatCompact(previousValue)} | Current:{" "}48 {formatCompact(value)}49 </p>50 </CardContent>51 </Card>52 );53}
Size variants
Compact KPI card with formatted value and up trend.
128.3K
128.3K
128.3K
128.3K
128.3K
128.3K
size-example.tsx
1"use client";23import { useEffect, useState } from "react";4import { LiveCounter, formatCompact } from "@/registry/ui";5import { Card, CardContent } from "@/components/ui/card";67const useLiveDemoCounter = (initialValue = 128_340) => {8 const [counterState, setCounterState] = useState({9 value: initialValue,10 previousValue: initialValue,11 });1213 useEffect(() => {14 const id = setInterval(() => {15 setCounterState((prev) => {16 const delta = Math.round((Math.random() - 0.35) * 2200);17 return {18 previousValue: prev.value,19 value: Math.max(95_000, prev.value + delta),20 };21 });22 }, 1500);2324 return () => clearInterval(id);25 }, []);2627 return counterState;28}2930export function LiveCounterSizeExample() {31 const { value, previousValue } = useLiveDemoCounter();32 const sizes = ["xs", "sm", "md", "lg", "xl", "2xl"] as const;3334 return (35 <Card className="w-full">36 <CardContent className="grid grid-cols-1 gap-3 md:grid-cols-2">37 {sizes.map((size) => (38 <div39 key={size}40 className="min-w-0 rounded-lg border border-border/60 bg-muted/20 p-4"41 >42 <LiveCounter43 value={value}44 previousValue={previousValue}45 label={size}46 formatValue={formatCompact}47 suffix="+"48 showTrend49 size={size}50 className="max-w-[18rem]"51 />52 </div>53 ))}54 </CardContent>55 </Card>56 );57}
Trend variants
Compact KPI card with formatted value and up trend.
129.3K
127.3K
128.3K
128.3K
glow off
trend-example.tsx
1"use client";23import { useEffect, useState } from "react";4import { LiveCounter, formatCompact } from "@/registry/ui";5import { Card, CardContent } from "@/components/ui/card";67const TREND_VARIANTS = [8 {9 key: "up",10 label: "Up",11 getValue: (value: number) => value + 1000,12 getPrevious: (value: number) => value,13 glow: true,14 },15 {16 key: "down",17 label: "Down",18 getValue: (value: number) => value - 1000,19 getPrevious: (value: number) => value,20 glow: true,21 },22 {23 key: "neutral",24 label: "Neutral",25 getValue: (value: number) => value,26 getPrevious: (value: number) => value,27 glow: true,28 },29 {30 key: "live",31 label: "Live",32 getValue: (value: number) => value,33 getPrevious: (_value: number, previousValue: number) => previousValue,34 glow: false,35 },36] as const;3738const useLiveDemoCounter = (initialValue = 128_340) => {39 const [counterState, setCounterState] = useState({40 value: initialValue,41 previousValue: initialValue,42 });4344 useEffect(() => {45 const id = setInterval(() => {46 setCounterState((prev) => {47 const delta = Math.round((Math.random() - 0.35) * 2200);48 return {49 previousValue: prev.value,50 value: Math.max(95_000, prev.value + delta),51 };52 });53 }, 1500);5455 return () => clearInterval(id);56 }, []);5758 return counterState;59};6061export function LiveCounterTrendExample() {62 const { value, previousValue } = useLiveDemoCounter();6364 return (65 <Card className="w-full">66 <CardContent className="grid grid-cols-1 gap-3 sm:grid-cols-2">67 {TREND_VARIANTS.map((variant) => (68 <div69 key={variant.key}70 className="min-w-0 rounded-lg border border-border/60 bg-muted/20 p-4"71 >72 <LiveCounter73 value={variant.getValue(value)}74 previousValue={variant.getPrevious(value, previousValue)}75 label={variant.label}76 formatValue={formatCompact}77 showTrend78 glow={variant.glow}79 size="lg"80 />81 {variant.key === "live" && (82 <p className="mt-2 font-mono text-[10px] text-muted-foreground uppercase tracking-wide">83 glow off84 </p>85 )}86 </div>87 ))}88 </CardContent>89 </Card>90 );91}
Installation & source
Install via the shadcn CLI or copy the registry files manually.
bash
npx shadcn@latest add @tt-ui/live-counter
Props
| Name | Type | Default | Description |
|---|---|---|---|
| value | number | required | Current numeric value to display |
| previousValue | number | undefined | Prior value used to derive trend when `showTrend` is true |
| formatValue | (value: number) => string | formatWithCommas | Formatter for the displayed number |
| prefix | ReactNode | undefined | Text or node before the number |
| suffix | ReactNode | undefined | Text or node after the number |
| size | LiveCounterSize | "md" | Typography scale: xs through 2xl |
| label | string | undefined | Optional label rendered above the value |
| showTrend | boolean | false | Show trend icon when `previousValue` is provided (derived from value vs previousValue) |
| animationDuration | number | 800 | Count animation duration in milliseconds |
| glow | boolean | true | Brief glow pulse when value changes |
| className | string | undefined | Classes on the outer shell |
| numberClassName | string | undefined | Classes on the number element |
| aria-label | string | undefined | Accessible label for screen readers |