# Stat story (goodtabs UI)

A pinned stage where big numbers count up one at a time as you scroll.

## Install

```bash
npx shadcn@latest add https://ui.goodtabs.co/r/stat-story.json
```

Or, with the @goodtabs namespace configured in components.json: `npx shadcn@latest add @goodtabs/stat-story`

## Usage

```tsx
import { StatStory, StatStoryItem } from "@/components/ui/stat-story"

<StatStory title="The year in numbers">
  <StatStoryItem label="Uptime" value={99.9} decimals={1} suffix="%">
    One grey line on what the number means.
  </StatStoryItem>
</StatStory>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| title | `ReactNode` |  | The section heading on StatStory, and its accessible name. |
| pace | `number` | `1` | Scroll distance per fact on StatStory, as a multiple of the viewport height. |
| value | `number` |  | The final number on StatStoryItem, required and positive. |
| decimals | `number` | `0` | Decimal places, fixed through the count. |
| prefix | `string` |  | Shown before the number, such as a currency sign. |
| suffix | `string` |  | Shown after the number at a smaller size, such as a unit. |
| label | `string` |  | Short mono label above the number. |
| children | `ReactNode` |  | The one grey line under the number. |

## When to use

- Three or four facts that deserve a beat each
- A results section on a case study or annual review
- Once on a landing page, where the numbers are the argument

## When not to use

- For figures people need to compare: use a stat grid or a table
- For more than four facts, which turns a story into a chore
- Inside a scaled or transformed parent, which breaks ScrollTrigger's measuring

## Notes for agents

- GSAP installs from npm under GSAP's own licence. No GSAP code ships in this registry
- Numbers render at their final value on the server. Screen readers always get that value from a hidden copy, never a value mid-count
- Below 768px, under reduced motion and with no script it is a plain grid of final values
- The first number counts as the stage scrolls in, so leave a lead-in above it. At the very top of a page it simply starts finished
- title labels the section. Without one, pass aria-label. It pins against the window, so it must not sit inside an overflow container or a CSS transform

## The maths

- Places that are not filled yet show as dim zeros copied from the final value, and tabular figures make every digit one width, so the text is the same width at every frame.
- Each hand-over is 0.35 rest, 0.15 out, then 0.5 of count with the fade-in inside it, so no two facts are ever on screen together.
- Pin distance is ((n - 1) + 0.35) × pace × viewport height, so four facts at pace 1 scroll 3.35 screens.

Docs: https://ui.goodtabs.co/components/stat-story