# Stats Section v5

> A response-time stats section written for non-technical readers. The title sits on the left and a plain-language description on the right. Below them, a histogram of response times, colored green, amber and red by speed with a matching strip along the axis, has dashed lines marking the typical response time and the time 99% of requests finish under. Hovering a bar shows what share of requests finished by then and highlights the matching line. Four stats sit under the chart in a fully bordered, square-cornered grid. 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-v5).

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

## Install

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

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

## Dependencies

- npm packages: `recharts`
- Registry items: `chart`

## Files

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

```tsx
import StatsSectionV5 from "./components/stats-section-v5"

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

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

```tsx
import { sectionHeader } from "../lib/config"
import LatencyPanel from "./latency-panel"

export default function StatsSectionV5() {
  return (
    <section className="py-16 md:py-24">
      <div className="mx-auto max-w-6xl px-6">
        <div className="grid gap-4 md:grid-cols-2 md:items-start md:gap-12">
          <h2 className="max-w-md text-3xl leading-[1.15] font-medium tracking-tight text-foreground md:text-4xl">
            {sectionHeader.title}
          </h2>
          <p className="max-w-md text-[0.9375rem] leading-relaxed text-muted-foreground md:justify-self-end">
            {sectionHeader.subtitle}
          </p>
        </div>

        <LatencyPanel />
      </div>
    </section>
  )
}
```

### `registry/new-york/blocks/stats-section-v5/components/latency-chart.tsx`

```tsx
"use client"

import { type PointerEvent, useState } from "react"
import { Bar, BarChart, Cell, ReferenceLine, XAxis, YAxis } from "recharts"

import { type ChartConfig, ChartContainer } from "@/components/ui/chart"
import { cn } from "@/lib/utils"

import {
  type Band,
  BAND_COLORS,
  BUCKET_MS,
  bucketOf,
  latencyBuckets,
  type Marker,
  P50_MS,
  P99_MS,
} from "../lib/config"

const chartConfig = {
  p50: { label: "Fast", color: BAND_COLORS.p50 },
  p99: { label: "Slower", color: BAND_COLORS.p99 },
  tail: { label: "Slowest 1%", color: BAND_COLORS.tail },
} satisfies ChartConfig

const BAR_OPACITY = 0.8
/** Bars outside the hovered bar's band fade to this opacity. */
const DIMMED = 0.2
/** Opacity of a reference line while the other one has focus. */
const MUTED_LINE = 0.2

const AXIS_TICKS = [0, 100, 200]
/** The hover handle starts on the median line, so the first hover slides out from it. */
const MEDIAN_INDEX = bucketOf(P50_MS) / BUCKET_MS
const peak = Math.max(...latencyBuckets.map((bucket) => bucket.requests))
/** Room above the tallest bar for the reference line labels. */
const HEADROOM = 1.3
/** Height recharts gives the x-axis, so the hover handle stops at the baseline. */
const AXIS_HEIGHT = 30
const BANDS: Band[] = ["p50", "p99", "tail"]
/** Buckets per band, so each strip segment spans exactly its bars. */
const bandWidths = BANDS.map(
  (band) => latencyBuckets.filter((bucket) => bucket.band === band).length
)

function AxisTick({
  x,
  y,
  payload,
}: {
  x?: number
  y?: number
  payload?: { value: number }
}) {
  const value = payload?.value ?? 0

  return (
    <text
      x={x}
      y={y}
      dy={14}
      textAnchor={value === 0 ? "start" : "middle"}
      className="fill-muted-foreground text-xs tabular-nums"
    >
      {value} ms
    </text>
  )
}

/** Vertical step between the two reference line labels, so they never overlap. */
const LABEL_ROW = 18

function MarkerLabel({
  viewBox,
  text,
  muted,
  side,
  row,
}: {
  viewBox?: { x?: number; y?: number }
  text: string
  muted: boolean
  /** Which side of its line the label sits on. */
  side: "right" | "left"
  row: number
}) {
  const x = viewBox?.x ?? 0
  const y = viewBox?.y ?? 0

  return (
    <text
      x={side === "right" ? x + 6 : x - 6}
      y={y + 4 + row * LABEL_ROW}
      textAnchor={side === "right" ? "start" : "end"}
      dominantBaseline="hanging"
      className={cn(
        "fill-foreground text-xs font-medium tabular-nums transition-opacity duration-300",
        muted && "opacity-20"
      )}
    >
      {text}
    </text>
  )
}

function formatShare(share: number) {
  return share >= 99 ? share.toFixed(1) : Math.round(share).toString()
}

function barOpacity(index: number, band: Band, activeIndex: number | null) {
  if (activeIndex === null) return BAR_OPACITY
  if (index === activeIndex) return 1
  const activeBand = latencyBuckets[activeIndex].band
  return band === activeBand ? BAR_OPACITY : DIMMED
}

export function LatencyChart({
  activeIndex,
  focus,
  onActiveChange,
  className,
}: {
  activeIndex: number | null
  focus: Marker | null
  onActiveChange: (index: number | null) => void
  className?: string
}) {
  // Keep the last hovered bucket so the label holds its text while it fades out.
  const [shownIndex, setShownIndex] = useState(MEDIAN_INDEX)
  if (activeIndex !== null && activeIndex !== shownIndex) {
    setShownIndex(activeIndex)
  }
  const shown = latencyBuckets[shownIndex]
  const handleLeft = ((shownIndex + 0.5) / latencyBuckets.length) * 100
  const flipLabel = handleLeft > 70

  function handlePointerMove(event: PointerEvent<HTMLDivElement>) {
    const rect = event.currentTarget.getBoundingClientRect()
    const ratio = (event.clientX - rect.left) / rect.width
    const index = Math.floor(ratio * latencyBuckets.length)
    onActiveChange(Math.min(Math.max(index, 0), latencyBuckets.length - 1))
  }

  return (
    <div
      className={cn("relative h-full w-full touch-pan-y", className)}
      onPointerMove={handlePointerMove}
      onPointerLeave={() => onActiveChange(null)}
    >
      <ChartContainer
        config={chartConfig}
        className="aspect-auto h-full w-full [&_.recharts-bar-rectangle_path]:transition-[fill-opacity] [&_.recharts-bar-rectangle_path]:duration-300 [&_.recharts-reference-line_line]:transition-[stroke-opacity] [&_.recharts-reference-line_line]:duration-300"
      >
        <BarChart
          data={latencyBuckets}
          margin={{ left: 0, right: 0, top: 0, bottom: 0 }}
          barCategoryGap={2}
        >
          <XAxis
            dataKey="ms"
            ticks={AXIS_TICKS}
            interval={0}
            tickLine={false}
            axisLine={{ stroke: "var(--border)" }}
            height={AXIS_HEIGHT}
            tick={<AxisTick />}
          />
          <YAxis hide domain={[0, peak * HEADROOM]} />
          <Bar
            dataKey="requests"
            radius={[2, 2, 0, 0]}
            animationDuration={900}
            animationEasing="ease-out"
          >
            {latencyBuckets.map((bucket, index) => (
              <Cell
                key={bucket.ms}
                fill={`var(--color-${bucket.band})`}
                fillOpacity={barOpacity(index, bucket.band, activeIndex)}
              />
            ))}
          </Bar>
          <ReferenceLine
            x={bucketOf(P50_MS)}
            stroke="var(--foreground)"
            strokeDasharray="4 4"
            strokeOpacity={focus === "p99" ? MUTED_LINE : 1}
            label={
              <MarkerLabel
                text={`Typical · ${P50_MS} ms`}
                muted={focus === "p99"}
                side="right"
                row={0}
              />
            }
          />
          <ReferenceLine
            x={bucketOf(P99_MS)}
            stroke="var(--foreground)"
            strokeDasharray="4 4"
            strokeOpacity={focus === "p50" ? MUTED_LINE : 1}
            label={
              <MarkerLabel
                text={`99% under ${P99_MS} ms`}
                muted={focus === "p50"}
                side="left"
                row={1}
              />
            }
          />
        </BarChart>
      </ChartContainer>

      {/* Speed strip along the axis, so the thin red tail stays visible. */}
      <div
        aria-hidden
        className="pointer-events-none absolute inset-x-0 flex h-[3px]"
        style={{ bottom: AXIS_HEIGHT - 4 }}
      >
        {BANDS.map((band, index) => (
          <span
            key={band}
            className={cn(
              "h-full transition-opacity duration-300",
              activeIndex !== null &&
                latencyBuckets[activeIndex].band !== band &&
                "opacity-20"
            )}
            style={{
              flexGrow: bandWidths[index],
              backgroundColor: BAND_COLORS[band],
            }}
          />
        ))}
      </div>

      <div
        className={cn(
          "pointer-events-none absolute top-0 border-l border-foreground transition-[left,opacity] duration-200 ease-out",
          activeIndex === null ? "opacity-0" : "opacity-100"
        )}
        style={{ left: `${handleLeft}%`, bottom: AXIS_HEIGHT }}
      >
        <div
          className={cn(
            "absolute top-14 flex items-center gap-1.5 rounded-md border border-border bg-background px-2.5 py-1.5 text-xs font-medium whitespace-nowrap text-foreground tabular-nums shadow-sm",
            flipLabel ? "right-2" : "left-2"
          )}
        >
          <span
            aria-hidden
            className="size-2 rounded-full transition-colors duration-200"
            style={{ backgroundColor: BAND_COLORS[shown.band] }}
          />
          {formatShare(shown.doneBy)}% under {shown.ms + BUCKET_MS} ms
        </div>
      </div>
    </div>
  )
}
```

### `registry/new-york/blocks/stats-section-v5/components/latency-panel.tsx`

```tsx
"use client"

import { useState } from "react"

import { BAND_COLORS, latencyBuckets, markerFor, stats } from "../lib/config"
import { LatencyChart } from "./latency-chart"

export default function LatencyPanel() {
  const [activeIndex, setActiveIndex] = useState<number | null>(null)
  const focus =
    activeIndex === null ? null : markerFor(latencyBuckets[activeIndex].band)

  return (
    <>
      <div
        aria-hidden
        className="mt-12 h-56 text-muted-foreground sm:h-64 md:mt-16 md:h-80"
      >
        <LatencyChart
          activeIndex={activeIndex}
          focus={focus}
          onActiveChange={setActiveIndex}
        />
      </div>

      <dl className="mt-12 grid grid-cols-2 border-t border-l border-border md:grid-cols-4">
        {stats.map((stat) => (
          <div
            key={stat.id}
            className="flex flex-col gap-2 border-r border-b border-border p-4 md:p-6"
          >
            <dt className="flex items-start gap-2 text-sm text-muted-foreground">
              {stat.marker && (
                <span
                  aria-hidden
                  className="mt-2.5 w-4 shrink-0 border-t-2 border-dashed"
                  style={{ borderColor: BAND_COLORS[stat.marker] }}
                />
              )}
              {stat.label}
            </dt>
            <dd className="text-3xl font-semibold tracking-tight text-foreground tabular-nums md:text-4xl">
              {stat.value}
            </dd>
          </div>
        ))}
      </dl>
    </>
  )
}
```

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

```ts
export const sectionHeader = {
  title: "Most requests finish faster than a blink.",
  subtitle:
    "We timed every request to Acme over the last 24 hours. 90% finished in under 100 ms, about one blink. Green bars finished faster than typical, amber bars took longer, and red bars are the slowest 1%.",
} as const

/** Width of one histogram bucket, in milliseconds. */
export const BUCKET_MS = 5
const BUCKETS = 48
const TOTAL_REQUESTS = 2_100_000_000

/** Log-normal latency: the median is e^MU, the spread is SIGMA. */
const MEDIAN_MS = 48
const MU = Math.log(MEDIAN_MS)
const SIGMA = 0.55
/** z-score of the 99th percentile of a standard normal distribution. */
const Z_99 = 2.326

export const P50_MS = MEDIAN_MS
export const P99_MS = Math.round(Math.exp(MU + Z_99 * SIGMA))

export type Band = "p50" | "p99" | "tail"

/**
 * Speed colors, green to red. Mid-tone oklch values so they read on both
 * light and dark backgrounds without a theme variable.
 */
export const BAND_COLORS: Record<Band, string> = {
  p50: "oklch(0.72 0.17 155)",
  p99: "oklch(0.78 0.16 75)",
  tail: "oklch(0.64 0.21 25)",
}

export type LatencyBucket = {
  /** Start of the bucket, in milliseconds. */
  ms: number
  requests: number
  band: Band
  /** Share of all requests, 0 to 100, that finish by the end of the bucket. */
  doneBy: number
}

/** Abramowitz and Stegun 7.1.26 approximation of the error function. */
function erf(x: number) {
  const t = 1 / (1 + 0.3275911 * Math.abs(x))
  const poly =
    t *
    (0.254829592 +
      t *
        (-0.284496736 +
          t * (1.421413741 + t * (-1.453152027 + t * 1.061405429))))
  const value = 1 - poly * Math.exp(-x * x)
  return x < 0 ? -value : value
}

function logNormalShareBy(ms: number) {
  const z = (Math.log(ms) - MU) / SIGMA
  return 0.5 * (1 + erf(z / Math.SQRT2))
}

function logNormalDensity(ms: number) {
  const x = Math.max(ms, 0.5)
  const z = (Math.log(x) - MU) / SIGMA
  return Math.exp(-0.5 * z * z) / (x * SIGMA * Math.sqrt(2 * Math.PI))
}

function bandFor(ms: number): Band {
  if (ms < P50_MS) return "p50"
  if (ms < P99_MS) return "p99"
  return "tail"
}

/** Requests per bucket, drawn from the same curve that sets P50_MS and P99_MS. */
export const latencyBuckets: LatencyBucket[] = Array.from(
  { length: BUCKETS },
  (_, index) => {
    const ms = index * BUCKET_MS
    const center = ms + BUCKET_MS / 2
    return {
      ms,
      requests: Math.round(
        TOTAL_REQUESTS * logNormalDensity(center) * BUCKET_MS
      ),
      band: bandFor(ms),
      doneBy: logNormalShareBy(ms + BUCKET_MS) * 100,
    }
  }
)

/** Snaps a latency to the start of its bucket so it lines up with a bar. */
export function bucketOf(ms: number) {
  return Math.floor(ms / BUCKET_MS) * BUCKET_MS
}

export type Marker = "p50" | "p99"

/** The reference line a bucket belongs to: the median, or the 99th percentile past it. */
export function markerFor(band: Band): Marker {
  return band === "p50" ? "p50" : "p99"
}

export type Stat = {
  id: string
  value: string
  label: string
  /** Stats with a marker match a dashed reference line on the chart. */
  marker?: Marker
}

export const stats: Stat[] = [
  {
    id: "p50",
    value: `${P50_MS} ms`,
    label: "Typical response",
    marker: "p50",
  },
  {
    id: "p99",
    value: `${P99_MS} ms`,
    label: "99% under",
    marker: "p99",
  },
  { id: "requests", value: "2.1B", label: "Requests a day" },
  { id: "uptime", value: "99.99%", label: "Uptime" },
]
```
