One headline figure on a card, with a label, a change line in words, a caption and a trend.

Anatomy

A tile shows an overline label in slate capitals, the value as a small numeral, a change line with an arrow and your sentence, and a caption for the period. Only the label and the value are required. The tile uses the Card's surface, and its padding follows density.

Example

Spending

$1,284.50

Up 12% from last month

Across all accounts

Follows the page theme and corners
import { StatTile } from '@snapshot-ui/react';
import type { ReactElement } from 'react';

export default function StatTileBasic(): ReactElement {
  return (
    <div className="tile-row">
      <StatTile
        label="Spending"
        value="$1,284.50"
        change={{ direction: 'up', label: 'Up 12% from last month' }}
        caption="Across all accounts"
      />
    </div>
  );
}

The change line

The line is slate by default. Set tone to gain only for money that arrived, and to warning only for a state that needs a look. Direction never sets the color: "Down 12%" is good for spending and bad for income. The sentence carries the meaning, and the arrow repeats it.

Example

Spending

$1,284.50

Up 12% from last month

Across all accounts

Income

$5,420.00

Up $420 from last month

Paid into checking

Savings

$12,000.00

No change in 3 weeks

Emergency fund

Follows the page theme and corners
import { StatTile } from '@snapshot-ui/react';
import type { ReactElement } from 'react';

export default function StatTileTones(): ReactElement {
  return (
    <div className="tile-row">
      {/* Neutral: more spending is not good or bad on its own, so the sentence says it and the color does not. */}
      <StatTile
        label="Spending"
        value="$1,284.50"
        change={{ direction: 'up', label: 'Up 12% from last month' }}
        caption="Across all accounts"
      />
      {/* Gain: money that arrived. */}
      <StatTile
        label="Income"
        value="$5,420.00"
        change={{ direction: 'up', label: 'Up $420 from last month', tone: 'gain' }}
        caption="Paid into checking"
      />
      {/* Warning: a state that needs a look. */}
      <StatTile
        label="Savings"
        value="$12,000.00"
        change={{ direction: 'flat', label: 'No change in 3 weeks', tone: 'warning' }}
        caption="Emergency fund"
      />
    </div>
  );
}

A change against a period

To say how a figure moved since the last period, write the change as the sentence you would say, with the period in it, and pass it as the change line:

<StatTile
  label="Spent"
  value="$4,220.00"
  caption="October"
  change={{ direction: 'up', label: 'Up $120 vs September' }}
/>

It is a line and not a chip. A filled pill draws a delta as a badge, which reads as a state, and its fill would need a color; up and down mean nothing by themselves, since more spending and more income point the same way. The line keeps the tile's weight on its figure, reads as one sentence with the arrow repeating it, and takes a tone only for a meaning. The Insights recipe draws one per tile while its page compares.

A trend

Pass a small drawing, up to 96 by 32, in trend. The Sparkline is made for it. It sits at the end of the value's row, and moves under the caption when the tile has less than 18rem of content. It is hidden from assistive technology unless it names itself, since the text already says the change.

Example

Net worth

$48,210.00

Up 3% this quarter

All accounts, less what you owe

Follows the page theme and corners
import { Sparkline } from '@snapshot-ui/charts';
import { StatTile } from '@snapshot-ui/react';
import type { ReactElement } from 'react';
import { NET_WORTH } from './sparkline-data';

export default function StatTileTrend(): ReactElement {
  return (
    <div className="tile-row">
      <StatTile
        label="Net worth"
        value="$48,210.00"
        change={{ direction: 'up', label: 'Up 3% this quarter' }}
        caption="All accounts, less what you owe"
        trend={<Sparkline data={NET_WORTH} />}
      />
    </div>
  );
}

A gauge as the trend

A 32px Gauge fits the trend's slot, for a share rather than a change over time: credit used of a limit, a goal saved. A gauge is a meter that names itself, so it is read in the tile; give it an aria-label that says what share it is, and its value text beside it.

Example

Credit used

$1,150.00

Of $5,000 across your cards

Follows the page theme and corners
import { Gauge, StatTile } from '@snapshot-ui/react';
import type { ReactElement } from 'react';

export default function GaugeTile(): ReactElement {
  return (
    <div className="tile-row">
      <StatTile
        label="Credit used"
        value="$1,150.00"
        caption="Of $5,000 across your cards"
        // A 32px ring fits the trend's 96 by 32 slot, named since the tile's words do not say the share.
        trend={
          <Gauge
            aria-label="Credit used, share of your limits"
            size="sm"
            value={23}
            valueLabel="23%"
            marks={[
              { value: 10, label: 'Best under 10%' },
              { value: 30, label: 'Keep it under 30%' },
            ]}
          />
        }
      />
    </div>
  );
}

href makes the whole tile one link, routed through RouterProvider as Routing sets up. Its name is everything it shows, in order. Put nothing focusable inside it: a link cannot hold a link or a button, and the console warns when one does.

Example

import { StatTile } from '@snapshot-ui/react';
import type { ReactElement } from 'react';

export default function StatTileLink(): ReactElement {
  return (
    <div className="tile-row">
      {/* The whole tile is one link, named by everything it shows. */}
      <StatTile
        href="#spending"
        label="Spending"
        value="$1,284.50"
        change={{ direction: 'up', label: 'Up 12% from last month' }}
        caption="Across all accounts"
      />
    </div>
  );
}

In a row

A tile is a size container, so it takes its width from its column, never from its content, and has no width of its own. Lay tiles in a grid such as repeat(auto-fit, minmax(16rem, 1fr)), not in a row that sizes them by content. Cap the minimum at the row's width, as below, so a column narrower than 16rem holds one tile and nothing scrolls sideways. Placed straight in a flex row a tile is 16rem wide and shrinks from there, since it takes flex: 0 1 16rem; give it flex: 1 1 16rem to share the row's room. In a flex column that basis would be its height, so give a tile there flex: none. In an auto grid column, give it a width.

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: var(--spacing-peer);
}

Usage

Pass every figure as a formatted string. The library formats no number or date, so the server's HTML is final in any locale. A tile without href renders in a server component with no client JavaScript. A linked tile is a client part.

import { StatTile } from '@snapshot-ui/react';

<StatTile
  label="Spending"
  value="$1,284.50"
  change={{ direction: 'up', label: 'Up 12% from last month' }}
  caption="Across all accounts"
  href="/spending"
/>;

Props

A plain StatTile accepts every attribute of an HTML element. A linked one is React Aria's Link, which forwards only id, data-*, lang, dir, hidden, style, aria-label, aria-labelledby, aria-describedby, aria-details, aria-current and the event handlers. Others, such as title or role, apply only to the plain tile.

caption
ReactNode

change
StatTileChange

className
string

href
string

Makes the whole tile a link.

label
ReactNode

level
2 | 3 | 4

The heading level of the label, when the tile should be in the page outline. Omitted, the label is a p.

trend
ReactNode

A Sparkline, or any small drawing. Hidden from assistive technology unless it names itself.

value
ReactNode

The figure, formatted.

direction
"down" | "flat" | "up"

The arrow, and the word the app writes.

label
string

The whole sentence, formatted: "Up 12% from last month". It carries the meaning; the arrow repeats it.

tone
"gain" | "neutral" | "warning"

Color only when the change means one of these.

Default: 'neutral'

Accessibility

The tile is read as its text, in order: the label, the value, the change and the caption. A linked tile is one link named by that text. The arrow is hidden, since the sentence says the change. level makes the label a heading, so the tile joins the page outline.

TabMoves to a linked tile, one stop for the whole tile.
EnterFollows the link.

Design rules

What keeps stat tiles in the system.

Show one figure per tile.

Say the change in words, in a sentence.

Give a caption for the period the figure covers.

Never color a change by its direction.

Never put a button or a link inside a linked tile.

Never pass a figure for the library to format. Every figure is your string.

esc

↑ ↓ to move↵ to open