Four-stat band whose values count up once in view, with hairline separators and an optional inverted dark variant.
npx shadcn@latest add @paragon/stats-bandAlso installs: number-ticker
"use client";
import * as React from "react";
import { useInView, useReducedMotion } from "motion/react";
import { cn } from "@/lib/utils";
import { NumberTicker } from "@/registry/paragon/ui/number-ticker";
const REVEAL =
"transition-[opacity,translate,filter] duration-500 ease-out motion-reduce:transition-none";
const HIDDEN = "translate-y-3 opacity-0 blur-[4px]";
const SHOWN = "translate-y-0 opacity-100 blur-[0px]";
const STATS: {
value: number;
suffix?: string;
label: string;
}[] = [
{ value: 99.99, suffix: "%", label: "Uptime over the last 12 months" },
{ value: 2.4, suffix: "B", label: "Events processed every day" },
{ value: 87, suffix: "ms", label: "Median query latency" },
{ value: 3200, suffix: "+", label: "Engineering teams on the platform" },
];
export interface StatsBandProps extends React.ComponentProps<"section"> {
/**
* Renders the band on the dark surface regardless of page theme —
* an inverted panel for light marketing pages.
*/
inverted?: boolean;
}
/**
* Four-stat band: values count up once on first view (tabular-nums, so
* nothing shifts mid-count), labels beneath, hairline separators between
* columns. `inverted` flips the band to the dark surface.
*/
export function StatsBand({
inverted = false,
className,
...props
}: StatsBandProps) {
const ref = React.useRef<HTMLElement>(null);
const inView = useInView(ref, { once: true, margin: "-80px" });
const reduced = useReducedMotion();
const revealed = inView || !!reduced;
return (
<section
ref={ref}
aria-label="Platform statistics"
className={cn(
"w-full bg-background px-4 py-14 text-foreground sm:px-6 md:py-20",
inverted && "dark",
className,
)}
{...props}
>
<div className="mx-auto w-full max-w-5xl">
<dl className="grid grid-cols-2 gap-x-6 gap-y-10 lg:grid-cols-4 lg:gap-x-0 lg:divide-x lg:divide-border">
{STATS.map((stat, i) => (
<div
key={stat.label}
className={cn(
"flex flex-col lg:px-8 lg:first:pl-0 lg:last:pr-0",
REVEAL,
revealed ? SHOWN : HIDDEN,
)}
style={{ transitionDelay: `${i * 80}ms` }}
>
<dd className="order-1 text-4xl font-semibold tracking-tight tabular-nums">
<NumberTicker
value={stat.value}
duration={0.9}
delay={0.15 + i * 0.08}
/>
{stat.suffix && (
<span className="text-muted-foreground">{stat.suffix}</span>
)}
</dd>
<dt className="order-2 mt-2 text-sm text-pretty text-muted-foreground">
{stat.label}
</dt>
</div>
))}
</dl>
</div>
</section>
);
}