# Stats Cards

> A row of stat cards, each with a label, a value, and an optional change badge, with loading and empty states.

- Name: `stats-cards`
- Status: beta
- Categories: dashboard, stats, cards
- Files: the install writes `stats-cards.tsx`, and any part files named `stats-cards-*.tsx`, to your `components` alias, `@/components` by default. Edit them freely. They are yours.
- Other registry items: `@txbrendan/card`, `@txbrendan/badge`, `@txbrendan/skeleton`. The install adds them.
- Gallery: https://txbrendan.dev/blocks/stats-cards
- Registry item: https://txbrendan.dev/r/stats-cards.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@txbrendan": "https://txbrendan.dev/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@txbrendan` in a public list that txbrendan/ui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @txbrendan/theme
   ```

3. Add the block. It also adds every component it uses:

   ```bash
   npx shadcn@latest add @txbrendan/stats-cards
   ```

## Use it for

- The top of a dashboard: two to four numbers people check first.
- Any place that shows a few key numbers side by side, like a report summary or an account page.
- Format each value and change yourself, so numbers, money, and dates read the way your app writes them.
- Pick each change's tone from what it means. Fewer errors is good news, so a drop there is success.

## Avoid it for

- More than eight numbers. Use a table, so people can sort and compare.
- Numbers over time. Use a chart, and put the latest value in a stat card beside it.
- A single number in running text. Write it in the sentence.

## Props

- `items`: `{ label: string; value: string; change?: string; tone?: "neutral" | "brand" | "danger" | "success" | "warning"; description?: string }[]`. One card per item, in order. Labels must be unique, since each one is a key.
- `loading`: `boolean`. Default `false`. Shows placeholder cards: one per item, or four when there are no items yet.
- `loadingText`: `string`. Default `"Loading stats"`. What the status line tells screen readers while loading.
- `emptyText`: `string`. Default `"No stats yet."`. Shown when there are no items and nothing is loading.
- `className`: `string`. Extra classes on the outer div, which holds the container query.

## Accessibility

- The cards are a list, so screen readers say how many stats there are. Each card reads its label first, then its value.
- The change is text in a badge. Its color adds meaning but never carries it alone.
- While loading, the list has aria-busy="true" and a status line says loadingText. That line stays on the page, outside the list, so screen readers hear it.
- The cards have no headings. If people should jump between them, put the block under a heading of your own.
- Nothing in the block takes focus. Put links and buttons near it, not inside a card.

## Examples

### Four stats

```tsx
<StatsCards
  items={[
    { label: "Active users", value: "12,480", change: "+12%", tone: "success", description: "since last week" },
    { label: "Revenue", value: "$48.2k", change: "+4%", tone: "success", description: "since last month" },
    { label: "Errors", value: "37", change: "+9", tone: "danger", description: "in the last hour" },
    { label: "Uptime", value: "99.98%", description: "this quarter" },
  ]}
/>
```

### While the numbers load

```tsx
<StatsCards items={stats ?? []} loading={stats === undefined} loadingText="Loading account stats" />
```

### With nothing to show yet

```tsx
<StatsCards items={[]} emptyText="Stats show up after your first sale." />
```
