# Stats Grid Bordered — React Stats Section

> A minimal hairline grid of serif numerals that rise through a mask on view; hovering a cell floods it with the foreground colour from the bottom edge and reveals a footnote.

- Category: [Stats](https://pixelpapa.com/components/stats.md)
- Tags: stats, grid, bordered, serif, minimal
- Dependencies: motion (plus React and Tailwind CSS v4)
- Last updated: 2026-11-13
- Live preview: https://pixelpapa.com/components/stats/stats-grid-bordered

## Install

```bash
npx shadcn@latest add "https://pixelpapa.com/r/stats-grid-bordered.json"
```

The file is written to `components/pixelpapa/stats-grid-bordered.tsx`. To install manually, run `npm install motion` and copy the source below into your project. It imports `cn` from `@/lib/utils`, which every shadcn project has.

## Source

```tsx
"use client";

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

const ease = [0.22, 1, 0.36, 1] as const;

type Stat = { index: string; value: string; unit?: string; label: string; footnote: string };

const defaultStats: Stat[] = [
  { index: "A", value: "1968", label: "Founded in Rotterdam", footnote: "Still independent, still family-owned." },
  { index: "B", value: "412", label: "Buildings completed", footnote: "Across 23 countries and every climate zone." },
  { index: "C", value: "86", unit: "%", label: "Projects delivered on budget", footnote: "Industry median is 31 percent." },
  { index: "D", value: "140", label: "Architects and engineers", footnote: "In four studios; nobody is outsourced." },
  { index: "E", value: "19", label: "National design awards", footnote: "Including three for social housing." },
  { index: "F", value: "0", unit: " kg", label: "Net operational carbon since 2021", footnote: "Per square metre, independently measured." },
];

export default function StatsGridBordered({ stats = defaultStats }: { stats?: Stat[] }) {
  return (
    <section className="w-full bg-background px-6 py-20 sm:py-28">
      <div className="mx-auto max-w-6xl">
        <div className="flex flex-col justify-between gap-6 border-b border-border pb-10 sm:flex-row sm:items-end">
          <h2 className="max-w-xl font-serif text-5xl leading-[1.05] text-balance text-foreground sm:text-6xl">
            Fifty-seven years, <em>measured</em>
          </h2>
          <p className="max-w-xs text-sm text-muted-foreground">
            Verdant &amp; Harrow is an architecture practice. We publish these figures every spring, whether or not they flatter us.
          </p>
        </div>

        <dl className="grid border-l border-border sm:grid-cols-2 lg:grid-cols-3">
          {stats.map((s, i) => (
            <div
              key={s.index}
              tabIndex={0}
              className="group relative isolate overflow-hidden border-r border-b border-border p-8 outline-none sm:p-10"
            >
              {/* The flood: a foreground panel that wipes up from the bottom edge. */}
              <span
                aria-hidden
                className="absolute inset-0 -z-10 translate-y-full bg-foreground transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:translate-y-0 group-focus-visible:translate-y-0 motion-reduce:transition-none"
              />
              <p className="flex items-center justify-between font-mono text-xs text-muted-foreground transition-colors duration-300 group-hover:text-background/60 group-focus-visible:text-background/60">
                <span>{s.index}</span>
                <span>0{i + 1} / 0{stats.length}</span>
              </p>
              {/* Observe the mask, not the numeral: the numeral starts clipped, so it would never intersect. */}
              <motion.dd initial="hidden" whileInView="shown" viewport={{ once: true, amount: 0.5 }} className="mt-12 overflow-hidden pb-2">
                <motion.span
                  variants={{ hidden: { y: "110%" }, shown: { y: 0 } }}
                  transition={{ duration: 0.9, delay: (i % 3) * 0.09 + Math.floor(i / 3) * 0.12, ease }}
                  className="block font-serif text-7xl leading-none text-foreground tabular-nums transition-colors duration-300 group-hover:text-background group-focus-visible:text-background sm:text-8xl"
                >
                  {s.value}
                  {s.unit && <span className={cn("text-[0.4em] italic", s.unit.startsWith(" ") && "ml-2")}>{s.unit.trim()}</span>}
                </motion.span>
              </motion.dd>
              <dt className="mt-4 font-medium text-foreground transition-colors duration-300 group-hover:text-background group-focus-visible:text-background">
                {s.label}
              </dt>
              <p className="mt-1 text-sm text-muted-foreground opacity-0 transition-all duration-300 group-hover:text-background/70 group-hover:opacity-100 group-focus-visible:text-background/70 group-focus-visible:opacity-100">
                {s.footnote}
              </p>
            </div>
          ))}
        </dl>
      </div>
    </section>
  );
}
```

## AI prompt

```text
Add the "Stats Grid Bordered" component from PixelPapa UI to this project.

What it is: A minimal hairline grid of serif numerals that rise through a mask on view; hovering a cell floods it with the foreground colour from the bottom edge and reveals a footnote.
Category: Stats. Depends on: motion.

1. Install it with the shadcn CLI:
   npx shadcn@latest add "https://pixelpapa.com/r/stats-grid-bordered.json"
   (If the CLI is unavailable, fetch that URL, write files[0].content to components/pixelpapa/stats-grid-bordered.tsx and install the listed dependencies.)
2. Import it from "@/components/pixelpapa/stats-grid-bordered" and place it where I describe below.
3. Keep its animation timings and accessibility attributes. Replace the demo copy and data with content that fits my product, and use my existing theme tokens (bg-background, text-foreground, bg-primary, border-border) rather than hard-coded colours.

Where to use it: <describe the page and position here>
```

## More stats

- [Stats Bar Race](https://pixelpapa.com/components/stats/stats-bar-race.md) (Pro): A bar-chart race: horizontal bars grow and re-rank with layout springs as the year advances on its own, with a play/pause control and a year scrubber.
- [Stats Count Up Row](https://pixelpapa.com/components/stats/stats-count-up-row.md): Four headline numbers in a divided row that count up from zero out of a blur when scrolled into view, each under a hairline that fills as it counts.
- [Stats Radial Progress](https://pixelpapa.com/components/stats/stats-radial-progress.md): Four concentric rings sweep to their values with spring easing beside a legend; hovering a row thickens its ring, dims the rest and swaps the centre readout.
- [Stats Split Quote](https://pixelpapa.com/components/stats/stats-split-quote.md) (Pro): A split section: an oversized serif stat counts up on the left with a before/after bar, while the customer quote on the right reveals word by word.
- [Stats Ticker Odometer](https://pixelpapa.com/components/stats/stats-ticker-odometer.md) (Pro): A live counter built from rolling digit columns: each digit springs to its new value as the total ticks up, with three smaller odometers underneath.
- [Stats With Sparklines](https://pixelpapa.com/components/stats/stats-with-sparklines.md) (Pro): Four stat cards whose sparklines draw in on view; move the pointer across a card to scrub its history and the headline number follows the marker.
