# Stats Section v6

> A support stats section with a bento grid of waffle charts. The left column has the title and description. The right column has one tile per support channel, sized by rank, with its share, wait time, and a small 10 by 10 grid. Each grid lights only that channel's squares, in the same spots they hold in the full 100, so the four grids add up to one chart. The two smallest channels show only the two rows that hold their squares, drawn at full tile width so they read at the same scale as the others. Lit squares fill in on load, then breathe in a slow diagonal shimmer. Static data, no API.

A shadcn/ui block from the [@uiception](https://uiception.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/uiception/stats-section-v6).

- Page, with a live preview: https://shadcn.exchange/r/uiception/stats-section-v6
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/uiception/stats-section-v6-light-ef7b7e72c8bd77e6.png
- Registry item: https://uiception.com/r/stats-section-v6.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://uiception.com/r/stats-section-v6.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Files

### `registry/new-york/blocks/stats-section-v6/page.tsx`

```tsx
import StatsSectionV6 from "./components/stats-section-v6"

export default function Page() {
  return <StatsSectionV6 />
}
```

### `registry/new-york/blocks/stats-section-v6/components/stats-section-v6.tsx`

```tsx
import { groups, sectionHeader } from "../lib/config"
import { type TileSize, TierTile } from "./tier-tile"
import "../styles/stats-section-v6.css"

/** Bento placement by rank: the biggest group gets the big tile. */
const tiles: { size: TileSize; className: string }[] = [
  { size: "feature", className: "col-span-2 md:row-span-2" },
  { size: "wide", className: "col-span-2" },
  { size: "compact", className: "col-span-1" },
  { size: "compact", className: "col-span-1" },
]

export default function StatsSectionV6() {
  return (
    <section className="py-16 md:py-24">
      <div className="mx-auto grid max-w-6xl gap-12 px-6 lg:grid-cols-12">
        <div className="flex flex-col lg:col-span-4">
          <h2 className="text-3xl leading-[1.15] font-medium tracking-tight text-foreground md:text-4xl">
            {sectionHeader.title}
          </h2>
          <p className="mt-4 text-[0.9375rem] leading-relaxed text-muted-foreground">
            {sectionHeader.description}
          </p>
        </div>

        <dl className="grid grid-cols-2 gap-px border border-border bg-border md:grid-cols-4 md:grid-rows-[repeat(2,16rem)] lg:col-span-8">
          {groups.map((group, index) => (
            <TierTile
              key={group.id}
              group={group}
              groupIndex={index}
              size={tiles[index].size}
              className={tiles[index].className}
            />
          ))}
        </dl>
      </div>
    </section>
  )
}
```

### `registry/new-york/blocks/stats-section-v6/components/tier-tile.tsx`

```tsx
import { cn } from "@/lib/utils"

import { type Group, zoomWindow } from "../lib/config"
import { MiniWaffle } from "./mini-waffle"

export type TileSize = "feature" | "wide" | "compact"

const tileClass = "flex flex-col justify-between gap-6 bg-background p-5"

const valueClass =
  "leading-none font-semibold tracking-tight text-foreground tabular-nums"

function TileLabel({ group, className }: { group: Group; className?: string }) {
  return (
    <dt className={cn("flex flex-col text-base", className)}>
      <span className="font-medium text-foreground">{group.channel}</span>
      <span className="text-muted-foreground">{group.wait}</span>
    </dt>
  )
}

export function TierTile({
  group,
  groupIndex,
  size,
  className,
}: {
  group: Group
  groupIndex: number
  size: TileSize
  className?: string
}) {
  if (size === "wide") {
    return (
      // On desktop the tile is a two-column grid: label above value on the
      // left, chart on the right. The chart column shrinks before it can
      // overlap the label. `md:contents` lets the dd's children join the grid.
      <div
        className={cn(
          tileClass,
          "md:grid md:grid-cols-[auto_minmax(0,1fr)] md:grid-rows-[auto_1fr] md:gap-x-6",
          className
        )}
      >
        <TileLabel group={group} className="md:col-start-1 md:row-start-1" />
        <dd className="flex items-center justify-between gap-6 md:contents">
          <span
            className={cn(
              valueClass,
              "text-4xl md:col-start-1 md:row-start-2 md:self-end md:text-5xl"
            )}
          >
            {group.share}%
          </span>
          <MiniWaffle
            groupIndex={groupIndex}
            className="w-44 gap-[3px] md:col-start-2 md:row-span-2 md:row-start-1 md:w-full md:max-w-44 md:self-center md:justify-self-end"
          />
        </dd>
      </div>
    )
  }

  // The feature and compact tiles read top to bottom: label, grid, value.
  // Their values sit on the bottom edge, so they line up across the row.
  // The compact tiles hold too few squares to read in a full grid, so they
  // draw only the band of rows that holds them, at full tile width.
  const isFeature = size === "feature"

  return (
    <div className={cn(tileClass, className)}>
      <TileLabel group={group} />
      <dd
        className={cn(
          "flex gap-6 md:flex-1 md:flex-col md:items-start",
          isFeature
            ? "flex-row-reverse items-center justify-between"
            : "flex-col"
        )}
      >
        <span className="flex w-full items-center md:flex-1">
          {isFeature ? (
            <MiniWaffle
              groupIndex={groupIndex}
              className="ml-auto w-44 max-w-full gap-[3px] md:ml-0 md:w-64"
            />
          ) : (
            <MiniWaffle
              groupIndex={groupIndex}
              rows={zoomWindow(groupIndex)}
              className="-mx-2 w-[calc(100%+1rem)] gap-[2px]"
            />
          )}
        </span>
        <span
          className={cn(
            valueClass,
            "shrink-0",
            isFeature ? "text-5xl md:text-6xl" : "text-3xl md:text-4xl"
          )}
        >
          {group.share}%
        </span>
      </dd>
    </div>
  )
}
```

### `registry/new-york/blocks/stats-section-v6/components/mini-waffle.tsx`

```tsx
import type { CSSProperties } from "react"

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

import { cellOrder, cells, GRID_SIZE, groups } from "../lib/config"

/** Delay between lit squares in the fill-in, in milliseconds. */
const FILL_STEP_MS = 10
/** Delay per diagonal step in the breathing shimmer, in milliseconds. */
const SHIMMER_STEP_MS = 120

export type RowWindow = { start: number; count: number }

/**
 * A 10-column grid that lights only one group's squares, in the same spots
 * they hold in the full 100. Laid side by side, the four grids add up to one
 * chart. `rows` draws only a band of rows, for groups too small to read in
 * the full grid.
 */
export function MiniWaffle({
  groupIndex,
  rows = { start: 0, count: GRID_SIZE },
  className,
}: {
  groupIndex: number
  rows?: RowWindow
  className?: string
}) {
  const color = groups[groupIndex].color
  const first = rows.start * GRID_SIZE
  const visible = cells.slice(first, first + rows.count * GRID_SIZE)

  return (
    <div aria-hidden className={cn("shrink-0", className)}>
      <div className="grid grid-cols-10 gap-[inherit]">
        {visible.map((cellGroup, offset) => {
          const index = first + offset

          if (cellGroup !== groupIndex) {
            return (
              <span
                key={index}
                className="aspect-square rounded-[2px] bg-muted"
              />
            )
          }

          return (
            <span
              key={index}
              className="ssv6-dot aspect-square rounded-[2px]"
              style={
                {
                  backgroundColor: color,
                  "--ssv6-fill-delay": `${cellOrder[index] * FILL_STEP_MS}ms`,
                  "--ssv6-shimmer-delay": `${
                    ((index % GRID_SIZE) + Math.floor(index / GRID_SIZE)) *
                    SHIMMER_STEP_MS
                  }ms`,
                } as CSSProperties
              }
            />
          )
        })}
      </div>
    </div>
  )
}
```

### `registry/new-york/blocks/stats-section-v6/lib/config.ts`

```ts
export const sectionHeader = {
  title: "Most customers get an answer in under five minutes.",
  description:
    "We tracked the last 10,000 support requests to Acme. In each grid, one square is one customer out of 100.",
} as const

export type Group = {
  id: string
  /** Where the customer got their answer. */
  channel: string
  /** How long they waited for it. */
  wait: string
  /** Customers out of 100. The four shares add up to 100. */
  share: number
  /** One teal hue, lightest for the fastest answer. Reads on light and dark backgrounds. */
  color: string
}

export const groups: Group[] = [
  {
    id: "help-center",
    channel: "Help center",
    wait: "Instant",
    share: 64,
    color: "oklch(0.82 0.11 190)",
  },
  {
    id: "chat",
    channel: "Live chat",
    wait: "Under 5 minutes",
    share: 24,
    color: "oklch(0.68 0.12 195)",
  },
  {
    id: "email",
    channel: "Email",
    wait: "Same day",
    share: 9,
    color: "oklch(0.54 0.11 205)",
  },
  {
    id: "engineer",
    channel: "Engineer",
    wait: "Within 2 days",
    share: 3,
    color: "oklch(0.42 0.09 215)",
  },
]

export const GRID_SIZE = 10

/** One entry per square, filled row by row from the fastest group to the slowest. */
export const cells: number[] = groups.flatMap((group, groupIndex) =>
  Array.from({ length: group.share }, () => groupIndex)
)

/** Rows shown in the zoomed-in view for the smallest groups. */
export const ZOOM_ROWS = 2

/** The band of rows that holds a group's squares, kept inside the grid. */
export function zoomWindow(groupIndex: number) {
  const firstRow = Math.floor(cells.indexOf(groupIndex) / GRID_SIZE)
  return {
    start: Math.min(firstRow, GRID_SIZE - ZOOM_ROWS),
    count: ZOOM_ROWS,
  }
}

/** Each square's position within its own group, used to stagger animations. */
export const cellOrder: number[] = groups.flatMap((group) =>
  Array.from({ length: group.share }, (_, index) => index)
)
```

### `registry/new-york/blocks/stats-section-v6/styles/stats-section-v6.css`

```css
/* Lit squares fill in one after another, then breathe. The breathing delay
   follows the square's diagonal, so a slow shimmer sweeps across each grid.
   `backwards` applies the fill's start state during its delay. */
.ssv6-dot {
  animation:
    ssv6-fill 420ms cubic-bezier(0.22, 1, 0.36, 1) var(--ssv6-fill-delay, 0ms)
      backwards,
    ssv6-breathe 3.6s ease-in-out calc(var(--ssv6-shimmer-delay, 0ms) + 1200ms)
      infinite;
}

@keyframes ssv6-fill {
  from {
    opacity: 0;
    transform: scale(0.4);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes ssv6-breathe {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.55;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ssv6-dot {
    animation: none;
  }
}
```
