Skip to the content

ComponentsData

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"

Usage

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

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.

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.

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%.

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.

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.

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.

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.

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.

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

Keyboard
KeyBehaviour

API

Statistic

Renders a div and passes it every other prop.

Statistic props
PropTypeDefaultDescription
labelrequiredReactNodeWhat the number counts, such as "Emails sent".
currencystringThe ISO 4217 code for format="currency", such as "EUR". Defaults to "USD".
format"number" | "currency" | "percent" | "compact"numbernumber (12,400), currency (with currency), percent (a fraction: 0.42 is 42%) or compact (12.4K).
formatOptionsNumberFormatOptionsIntl.NumberFormat options laid over the format's own, such as { maximumFractionDigits: 0 }.
helpTextReactNodeA short note after the trend, such as "since last week".
iconReactNodeAn icon in a 32 px circle at the end. Decorative: the label says what the number is.
iconClassNamestringClasses for the icon's circle, such as bg-info-tag text-info-tag-foreground.
invertTrendColorbooleanfalseColours a rise red and a fall green, for counts where less is better, such as bounces.
loadingbooleanfalseShows 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.
trendValuenumberThe size of the change as a fraction (0.12 is 12%), written as a percentage beside the arrow.
valuestring | number | nullThe 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.

StatisticGroup props
PropTypeDefaultDescription
variant"plain" | "card"cardThe 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.

The tokens its classes read. Change their values in your theme, and it follows.

Theme tokens
TokenUsed for
--cardbackground
--card-foregroundtext
--destructive-tag-foregroundtext
--foregroundtext
--info-tagbackground
--info-tag-foregroundtext
--muted-foregroundtext
--secondarybackground
--secondary-foregroundtext
--success-tag-foregroundtext