---
title: Stats
description: Metric layouts that put the important numbers first.
group: Blocks
order: 40
---

Clean ways to surface a few numbers without overexplaining them. Replace the values and labels with your own.

## Metric strip

Four key numbers in a centered row, stacking two by two on narrow widths.

```tsx
"use client";

import { cn } from "@/lib/utils";

type Stats01Props = {
  className?: string;
};

const STATS = [
  { value: "200+", label: "Blocks" },
  { value: "60", label: "Components" },
  { value: "12", label: "Themes" },
  { value: "99.9%", label: "Uptime" },
] as const;

export function Stats01({ className }: Stats01Props) {
  return (
    <section className={cn("@container w-full bg-background", className)}>
      <div className="mx-auto w-full max-w-4xl px-4 py-10 @[32rem]:px-6 @[32rem]:py-14">
        <ul className="grid grid-cols-2 gap-y-8 @[32rem]:grid-cols-4 @[32rem]:gap-0 @[32rem]:divide-x @[32rem]:divide-border">
          {STATS.map((stat) => (
            <li
              key={stat.label}
              className="flex flex-col items-center text-center"
            >
              <p className="text-3xl font-medium tracking-tight text-foreground @[32rem]:text-4xl">
                {stat.value}
              </p>
              <p className="mt-1.5 text-xs tracking-wide text-muted-foreground">
                {stat.label}
              </p>
            </li>
          ))}
        </ul>
      </div>
    </section>
  );
}
```

### Installation

```bash
npx @arctis-sh/@arctis-sh/ui@latest add stats-01
```

### Usage

```tsx
import { Stats01 } from "@/components/blocks/stats/stats-01"
```

```tsx
<Stats01 />
```

## Number ticker

Odometer-style digits that roll into place as the section enters view.

```tsx
"use client";

import { useEffect, useRef, useState } from "react";
import { cn } from "@/lib/utils";

type Stats02Props = {
  className?: string;
};

const STATS = [
  { value: "2400", suffix: "+", label: "Pages shipped" },
  { value: "99", suffix: "%", label: "Satisfaction" },
  { value: "60", suffix: "", label: "Components" },
] as const;

function TickerDigit({
  digit,
  active,
  delay,
}: {
  digit: string;
  active: boolean;
  delay: number;
}) {
  const n = Number(digit);
  if (Number.isNaN(n)) {
    return <span className="inline-block px-0.5">{digit}</span>;
  }

  return (
    <span className="relative inline-block h-[1em] w-[0.62em] overflow-hidden">
      <span
        aria-hidden
        className="absolute inset-x-0 top-0 flex flex-col will-change-transform"
        style={{
          transform: active ? `translateY(${-n}em)` : "translateY(0)",
          transition: active
            ? `transform 900ms cubic-bezier(0.22, 1, 0.36, 1) ${delay}ms`
            : "none",
        }}
      >
        {Array.from({ length: 10 }, (_, i) => (
          <span
            key={i}
            className="flex h-[1em] items-center justify-center tabular-nums"
          >
            {i}
          </span>
        ))}
      </span>
      <span className="invisible tabular-nums">{digit}</span>
    </span>
  );
}

function TickerValue({
  value,
  suffix,
  active,
  stagger,
}: {
  value: string;
  suffix: string;
  active: boolean;
  stagger: number;
}) {
  return (
    <p className="flex items-baseline text-4xl font-medium tracking-tight text-foreground @[32rem]:text-5xl">
      <span className="inline-flex" aria-label={`${value}${suffix}`}>
        {value.split("").map((digit, index) => (
          <TickerDigit
            key={`${digit}-${index}`}
            digit={digit}
            active={active}
            delay={stagger + index * 70}
          />
        ))}
      </span>
      {suffix ? (
        <span className="ml-0.5 text-[0.7em] tracking-tight">{suffix}</span>
      ) : null}
    </p>
  );
}

export function Stats02({ className }: Stats02Props) {
  const rootRef = useRef<HTMLElement>(null);
  const [active, setActive] = useState(false);

  useEffect(() => {
    const el = rootRef.current;
    if (!el) return;

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry?.isIntersecting) {
          setActive(true);
          observer.disconnect();
        }
      },
      { threshold: 0.35 },
    );

    observer.observe(el);
    return () => observer.disconnect();
  }, []);

  return (
    <section
      ref={rootRef}
      className={cn("@container w-full bg-background", className)}
    >
      <div className="mx-auto w-full max-w-4xl px-4 py-10 @[32rem]:px-6 @[32rem]:py-14">
        <ul className="grid grid-cols-1 gap-8 @[32rem]:grid-cols-3 @[32rem]:gap-0 @[32rem]:divide-x @[32rem]:divide-border">
          {STATS.map((stat, index) => (
            <li
              key={stat.label}
              className="flex flex-col items-center text-center"
            >
              <TickerValue
                value={stat.value}
                suffix={stat.suffix}
                active={active}
                stagger={index * 120}
              />
              <p className="mt-2 text-xs tracking-wide text-muted-foreground">
                {stat.label}
              </p>
            </li>
          ))}
        </ul>
      </div>
    </section>
  );
}
```

### Installation

```bash
npx @arctis-sh/@arctis-sh/ui@latest add stats-02
```

### Usage

```tsx
import { Stats02 } from "@/components/blocks/stats/stats-02"
```

```tsx
<Stats02 />
```

## Grid

Four metrics in a bordered grid, each paired with a short explanation.

```tsx
"use client";

import { cn } from "@/lib/utils";

type Stats03Props = {
  className?: string;
};

const STATS = [
  {
    value: "12k+",
    label: "Pages shipped",
    description: "Teams reuse the same blocks across marketing and product.",
  },
  {
    value: "4.9",
    label: "Average rating",
    description: "Clean defaults that still feel custom once themed.",
  },
  {
    value: "30s",
    label: "Install time",
    description: "Add a block with the CLI and wire it into a route.",
  },
  {
    value: "0",
    label: "Design debt",
    description: "One token system for light, dark, and everything between.",
  },
] as const;

export function Stats03({ className }: Stats03Props) {
  return (
    <section className={cn("@container w-full bg-background", className)}>
      <div className="mx-auto w-full max-w-4xl px-4 py-10 @[32rem]:px-6 @[32rem]:py-14">
        <ul className="grid grid-cols-1 gap-px overflow-hidden rounded-xl border border-border bg-border @[32rem]:grid-cols-2">
          {STATS.map((stat) => (
            <li
              key={stat.label}
              className="bg-background px-5 py-6 @[32rem]:px-6 @[32rem]:py-8"
            >
              <p className="text-3xl font-medium tracking-tight text-foreground">
                {stat.value}
              </p>
              <p className="mt-2 text-sm font-medium tracking-wide text-foreground">
                {stat.label}
              </p>
              <p className="mt-1.5 max-w-xs text-sm tracking-wide text-muted-foreground">
                {stat.description}
              </p>
            </li>
          ))}
        </ul>
      </div>
    </section>
  );
}
```

### Installation

```bash
npx @arctis-sh/@arctis-sh/ui@latest add stats-03
```

### Usage

```tsx
import { Stats03 } from "@/components/blocks/stats/stats-03"
```

```tsx
<Stats03 />
```
