# Statistic

Shows a number that matters, such as emails sent today, with its label and how it has changed.

- **Import:** `import { Statistic, StatisticGroup } from "@booleanpress/ui/statistic"`
- **Page:** <https://ui.booleanpress.com/components/statistic> · @booleanpress/ui 0.2.0

## Usage

Give a `label` and a `value`; the value is written with `Intl.NumberFormat` in the provider's locale.

```tsx
import { Statistic } from "@booleanpress/ui/statistic"

export function SentToday({ sent }: { sent: number }) {
  return <Statistic label="Emails sent today" value={sent} trend="up" trendValue={0.12} helpText="since yesterday" />
}
```

`format` is `number` (12,408), `currency` (with an ISO 4217 `currency` code), `percent` (the value is a fraction: 0.42 is 42%) or `compact` (12.4K); `formatOptions` lays any other `Intl.NumberFormat` option over it. A string `value` is shown as it is.

`trend` (`"up"` or `"down"`) adds an arrow and `trendValue`, a fraction written as a percentage: green for a rise and red for a fall, the other way round with `invertTrendColor` for counts where less is better, such as bounces. Screen readers hear the provider's `trendUp` or `trendDown` text ("Up 12%"), not the arrow. `helpText` follows the trend. `icon` adds a decorative icon in a circle, coloured with `iconClassName`. `loading` shows a placeholder in place of the value.

`variant="card"` draws the statistic on a bordered card. `StatisticGroup` lays statistics out in a row that wraps to fit, as cards unless its `variant` is `plain`.

## Examples

### Basic

A label and a number, written in the provider's locale.

```tsx
import { Statistic } from "@booleanpress/ui/statistic"

export default function StatisticBasic() {
  return <Statistic label="Emails sent today" value={12408} />
}
```

### Currency

`format="currency"` with a `currency` code; `formatOptions` drops the cents.

```tsx
import { Statistic } from "@booleanpress/ui/statistic"

export default function StatisticCurrency() {
  return (
    <div className="flex flex-wrap gap-12">
      <Statistic label="Monthly revenue" value={48250.5} format="currency" currency="EUR" />
      <Statistic label="Average order" value={64} format="currency" currency="USD" formatOptions={{ maximumFractionDigits: 0 }} />
    </div>
  )
}
```

### Compact

`format="compact"` writes 12,400 as 12.4K; `format="percent"` writes 0.428 as 42.8%.

```tsx
import { Statistic } from "@booleanpress/ui/statistic"

export default function StatisticCompact() {
  return (
    <div className="flex flex-wrap gap-12">
      <Statistic label="Subscribers" value={12400} format="compact" />
      <Statistic label="Emails this year" value={3_870_000} format="compact" />
      <Statistic label="Open rate" value={0.428} format="percent" />
    </div>
  )
}
```

### Trend

`trend` and `trendValue` add a coloured arrow and the change; `invertTrendColor` makes fewer bounces green.

```tsx
import { Statistic } from "@booleanpress/ui/statistic"

export default function StatisticTrend() {
  return (
    <div className="flex flex-wrap gap-12">
      <Statistic label="Delivered" value={11982} trend="up" trendValue={0.124} helpText="since last week" />
      <Statistic label="Open rate" value={0.381} format="percent" trend="down" trendValue={0.021} helpText="since last week" />
      <Statistic label="Bounces" value={86} trend="down" trendValue={0.3} invertTrendColor helpText="since last week" />
    </div>
  )
}
```

### With icon

`variant="card"` with an `icon` in a status colour.

```tsx
import { MailCheckIcon } from "lucide-react"
import { Statistic } from "@booleanpress/ui/statistic"

export default function StatisticWithIcon() {
  return (
    <Statistic
      variant="card"
      label="Delivered today"
      value={11982}
      trend="up"
      trendValue={0.124}
      helpText="since yesterday"
      icon={<MailCheckIcon />}
      iconClassName="bg-success-tag text-success-tag-foreground"
      className="w-full max-w-xs"
    />
  )
}
```

### Group

`StatisticGroup` lays four delivery figures out as cards in a row that wraps.

```tsx
import { InboxIcon, MailCheckIcon, MailWarningIcon, MousePointerClickIcon } from "lucide-react"
import { Statistic, StatisticGroup } from "@booleanpress/ui/statistic"

export default function StatisticGroupExample() {
  return (
    <StatisticGroup className="w-full">
      <Statistic label="Sent" value={12408} icon={<InboxIcon />} iconClassName="bg-info-tag text-info-tag-foreground" />
      <Statistic
        label="Delivered"
        value={0.966}
        format="percent"
        trend="up"
        trendValue={0.004}
        icon={<MailCheckIcon />}
        iconClassName="bg-success-tag text-success-tag-foreground"
      />
      <Statistic
        label="Opened"
        value={0.412}
        format="percent"
        trend="down"
        trendValue={0.018}
        icon={<MousePointerClickIcon />}
        iconClassName="bg-secondary text-secondary-foreground"
      />
      <Statistic
        label="Bounced"
        value={86}
        trend="down"
        trendValue={0.3}
        invertTrendColor
        icon={<MailWarningIcon />}
        iconClassName="bg-warning-tag text-warning-tag-foreground"
      />
    </StatisticGroup>
  )
}
```

### Loading

`loading` shows a placeholder for each value while the figures load.

```tsx
import { Statistic, StatisticGroup } from "@booleanpress/ui/statistic"

export default function StatisticLoading() {
  return (
    <StatisticGroup className="w-full">
      <Statistic label="Sent" loading />
      <Statistic label="Delivered" loading />
      <Statistic label="Opened" loading />
      <Statistic label="Bounced" loading />
    </StatisticGroup>
  )
}
```

### Sizes

`sm`, `default` and `lg` write the value at 16, 18 and 24 px.

```tsx
import { Statistic } from "@booleanpress/ui/statistic"

export default function StatisticSizes() {
  return (
    <div className="flex flex-wrap items-start gap-12">
      <Statistic size="sm" label="Small" value={12408} />
      <Statistic label="Default" value={12408} />
      <Statistic size="lg" label="Large" value={12408} />
    </div>
  )
}
```

## Accessibility

**Semantics.** A description list: the label is a `dt` and the value a `dd`, so a screen reader pairs them. The trend and help text are a second `dd` of the same label.

**Labels.** The arrow and the drawn percentage are hidden; screen readers hear the provider's `trendUp` or `trendDown` text instead ("Up 12.4%"). The icon is decorative. While loading, the statistic is `aria-busy` and the value reads as the provider's `loading` text.

**Focus.** A statistic takes no focus.

**Known limits.**

- Colour says whether a change is good; the words say only up or down. Say in `helpText` when the direction alone could mislead.
- A value that updates live is not announced. Wrap the statistic in your own `aria-live` region if people must hear changes.

### Keyboard

| Key | Behaviour |
| --- | --- |

## API

### Statistic

Renders a `div` and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` (required) | `ReactNode` |  | What the number counts, such as "Emails sent". |
| `currency` | `string` |  | The ISO 4217 code for `format="currency"`, such as `"EUR"`. Defaults to `"USD"`. |
| `format` | `"number" \| "currency" \| "percent" \| "compact"` | `number` | `number` (12,400), `currency` (with `currency`), `percent` (a fraction: 0.42 is 42%) or `compact` (12.4K). |
| `formatOptions` | `NumberFormatOptions` |  | `Intl.NumberFormat` options laid over the format's own, such as `{ maximumFractionDigits: 0 }`. |
| `helpText` | `ReactNode` |  | A short note after the trend, such as "since last week". |
| `icon` | `ReactNode` |  | An icon in a 32 px circle at the end. Decorative: the label says what the number is. |
| `iconClassName` | `string` |  | Classes for the icon's circle, such as `bg-info-tag text-info-tag-foreground`. |
| `invertTrendColor` | `boolean` | `false` | Colours a rise red and a fall green, for counts where less is better, such as bounces. |
| `loading` | `boolean` | `false` | Shows a placeholder in place of the value and the trend while the number loads. |
| `size` | `"default" \| "sm" \| "lg"` |  | The value's size: 16 px (`sm`), 18 px (`default`) or 24 px (`lg`). Defaults to the provider's `controlSize`. |
| `trend` | `"up" \| "down"` |  | The direction of the change: an arrow, coloured, with the words "Up" or "Down" for screen readers. |
| `trendValue` | `number` |  | The size of the change as a fraction (0.12 is 12%), written as a percentage beside the arrow. |
| `value` | `string \| number \| null` |  | The number, written with `format` in the provider's locale; a string is shown as it is. |
| `variant` | `"plain" \| "card"` |  | `plain` (the default) or `card`, a bordered card. Inside a `StatisticGroup` it defaults to the group's. |

### StatisticGroup

Renders a `div` and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"plain" \| "card"` | `card` | The look of the statistics inside, unless one sets its own: `card` (the default) or `plain`. |

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="statistic"` (Statistic), `data-slot="statistic-group"` (StatisticGroup), and `data-variant`, `data-size`, `data-trend`.

**Provider strings:** `loading`, `trendUp`, `trendDown` (`BooleanUIProvider`'s `strings`).

## Theming

The label is `--muted-foreground`, the value `--foreground` in bold. A rise is `--success-tag-foreground` and a fall `--destructive-tag-foreground`, the deep text colours of the status tags, which keep 4.5:1 on the page and on the card. The card is `--card` with a `--border` edge, a 12 px radius and a small shadow; the icon circle is `--secondary` unless `iconClassName` says otherwise.

| Token | Used for |
| --- | --- |
| `--card` | background |
| `--card-foreground` | text |
| `--destructive-tag-foreground` | text |
| `--foreground` | text |
| `--info-tag` | background |
| `--info-tag-foreground` | text |
| `--muted-foreground` | text |
| `--secondary` | background |
| `--secondary-foreground` | text |
| `--success-tag-foreground` | text |
