Pho Design System

Stat

A figure with its name: a label, a value, and, when it matters, how the value moved and against what. One tile answers “how many”; a Stat.Group lines several up as the row a page leads with, before the charts that explain them.

Import

import { Stat } from "@photon-ai/pho-ui/components/stat";

Basic

label names the figure, value is the figure already formatted (the tile does no rounding of its own), delta says how it moved and note what it moved against. The value takes the display size in the font’s proportional figures; label and note stay in the UI inks, so the number is the only loud thing.

Messages+30.0%
12,001vs. 9,231 the week before
<Stat
  label="Messages"
  value="12,001"
  delta={30}
  note="vs. 9,231 the week before"
/>

Group

Stat.Group lays tiles in one framed row with a hairline between each. Every tile takes an equal share and never gets narrower than 12rem; past that the row wraps, the hairlines follow, and a tile alone on the last line stretches across it, so five tiles read as one card at any width. A long label truncates rather than pushing its delta off the tile.

Visitors−42.0%
20.8Kvs. 36.2K prior
Page views−23.0%
151Kvs. 196K prior
Sessions−33.0%
35.2Kvs. 52.8K prior
Session duration+24.0%
8m 32svs. 6m 52s prior
Bounce rate−27.0%
23%vs. 31% prior
<Stat.Group aria-label="Traffic this week">
  <Stat label="Visitors" value="20.8K" delta={-42} note="vs. 36.2K prior" />
  <Stat label="Page views" value="151K" delta={-23} note="vs. 196K prior" />
  <Stat label="Sessions" value="35.2K" delta={-33} note="vs. 52.8K prior" />
  <Stat
    label="Session duration"
    value="8m 32s"
    delta={24}
    note="vs. 6m 52s prior"
  />
  <Stat
    label="Bounce rate"
    value="23%"
    delta={-27}
    deltaGood="down"
    note="vs. 31% prior"
  />
</Stat.Group>

Which way is good

A number delta is a percentage change, rendered signed with one decimal and an arrow. A string renders as written, and its leading sign sets the arrow; pass deltaDirection when the string can’t say. The badge’s ink says whether the move is good news, and deltaGood names the good direction: up by default, down for a bounce rate or a failure count, none when a change is just information. A flat delta has no arrow and no verdict.

Active users+30%
300
Failed deliveries−12.0%
20
Response ratio-10%
1:1
Numbers0.0%
4
<Stat label="Active users" value="300" delta="+30%" />
<Stat label="Failed deliveries" value="20" delta={-12} deltaGood="down" />
<Stat label="Response ratio" value="1:1" delta="-10%" deltaGood="none" />
<Stat label="Numbers" value="4" delta={0} />

Inside a card

A tile has no chrome of its own, so it sits in any card. For a row inside a card, variant="plain" keeps the hairlines between tiles and drops the frame. Card.Root drops its matching inset when the group is the first or last child, so the tiles run edge to edge.

Activity

Active users+30.0%
300
Active chats+30.0%
150
Response ratio−10.0%
1:1
<Card.Root>
  <Card.Header>
    <Card.Title>Activity</Card.Title>
  </Card.Header>
  <Stat.Group variant="plain" aria-label="Activity this week">
    <Stat label="Active users" value="300" delta={30} />
    <Stat label="Active chats" value="150" delta={30} />
    <Stat label="Response ratio" value="1:1" delta={-10} deltaGood="none" />
  </Stat.Group>
</Card.Root>

Props

Stat renders a <div> and forwards every div attribute.

Stat.Group renders a <div role="group">; give it an aria-label.

Accessibility

Best practices