Metric Card

A card that reads a title, a number, and a trend, charted as a line, bars over time, or a comparison across categories.

MetricCard pairs a headline number and trend with a chart of the data behind it. Give the card a fixed width and drop a VerticalBarChart, LineChart, or HorizontalBarChart inside as its child.

Choosing a chart

Pick the chart by the question the reader is asking.

ChartThe reader asksData
Line ChartIs it going up or down?One series over many time points, about 14 or more, read as a shape
Vertical Bar ChartHow did each period compare, and what made it up?About 7 to 14 periods, each a separate total, stacked when split into parts
Horizontal Bar ChartWhich is biggest, and by how much?Named categories with no time order, such as chains, apps, or tokens
  • Use bars for totals per period, such as sales per day or mints per week, and a line for a level that moves, such as price, TVL, or active users.
  • If the order along the axis doesn't matter, don't draw a line. A line implies the points between values exist.
  • Bars start at zero, so their lengths compare honestly. A line's scale covers the range of its data, which suits small changes in a large number.
  • Stack bars only when the split matters. If only the total matters, use a line or give Vertical Bar Chart a single series.
  • Past about 8 categories, show the top ones and group the rest as Other, or use a table.

Vertical Bar Chart

VerticalBarChart draws one bar per period. Each entry in series is a label and its data points; points from different series line up by timestamp, and a missing point counts as zero.

Mints

2,310
+4.1%
<MetricCard title="Mints" numValue={2310} trend="+4.1%" trendPositive>
<VerticalBarChart
  period="last-week"
  series={[
    {
      label: 'Mints',
      data: [
        { timestamp: '2024-01-01T12:00:00.000Z', value: 300 },
        { timestamp: '2024-01-02T12:00:00.000Z', value: 320 },
        // …one point per day
      ],
    },
  ]}
/>
</MetricCard>

period sets the date format in the tooltip and on the x axis, and how many placeholder bars to draw while loading. Bars start at zero and the tallest one fills the plot. Hovering a bar shows its date and value. suffix on the card renders a unit alongside the number, baseline-aligned. See Choosing a chart for when bars fit.

Stacked

Supply vs Borrow

450ETH
+5.2%
<MetricCard title="Supply vs Borrow" numValue={450} suffix="ETH" trend="+5.2%" trendPositive>
<VerticalBarChart
  period="last-week"
  series={[
    { label: 'Supply', data: supplyPoints },
    { label: 'Borrow', data: borrowPoints },
  ]}
/>
</MetricCard>

Pass two or more series and the bars stack, first series on top, with a 2px gap between parts. There is no prop to turn stacking on or off; the number of series decides it. The tooltip lists every part. A single series uses the strong blue; stacked series use light blue, then strong blue, then two more steps of the blue scale. Two or three parts read well; past that, the thin parts are hard to compare.

visibleSeries takes the indices of the series to draw, such as [1] to show only the second. Hidden series drop out of the bars, the scale, and the tooltip. Pair it with a control in the card's filter slot.

Migrating from Stacked Bar Chart

StackedBarChart is deprecated and will be removed in the next release. Until then it renders through VerticalBarChart. Replace it like this:

// Before
<StackedBarChart
  bars={buildSegmentBars(supplyPoints, borrowPoints)}
  allBars={buildSegmentBars(supplyPoints, borrowPoints)}
  period="last-week"
  userFilter="first"
  config={{ labels: ['Supply', 'Borrow'] }}
/>

// After
<VerticalBarChart
  period="last-week"
  series={[
    { label: 'Supply', data: supplyPoints },
    { label: 'Borrow', data: borrowPoints },
  ]}
  visibleSeries={[0]}
/>

Each series takes its ChartDataPoint array as data, so buildSegmentBars is no longer needed. userFilter becomes visibleSeries: all is the default, first is [0], and second is [1]. allBars has no replacement.

Horizontal Bar Chart

HorizontalBarChart compares one value across named categories. Each category gets a row: its name on the left and a bar whose length is its value.

Volume by Chain

2,950ETH
  • Base: 1,200
  • Ethereum: 800
  • Arbitrum: 520
  • Optimism: 430
<MetricCard title="Volume by Chain" numValue={2950} suffix="ETH">
<HorizontalBarChart
  data={[
    { label: 'Base', value: 1200 },
    { label: 'Ethereum', value: 800 },
    { label: 'Arbitrum', value: 520 },
    { label: 'Optimism', value: 430 },
  ]}
  xAxis
/>
</MetricCard>

Rows are drawn top to bottom in the order of data, so sort it first, usually largest first. The longest bar fills the row and the rest scale against it. Labels take the width of the longest one, up to 128px, and truncate past that. xAxis adds a value scale under the bars with dashed gridlines, starting at zero. Hovering a row shows its label and value. Pass formatValue to format that value, for example to add a unit; it is a function, so set it from a client component.

The card grows to fit the rows instead of holding its fixed height, so keep the list short; about 3 to 8 categories read well. For values over time, see Choosing a chart.

Line Chart

LineChart is the trend chart for MetricCard β€” a single series over time, scrubbable, with an optional gradient fill.

Total Volume

1,234,567
+12.3%
<MetricCard
title="Total Volume"
numValue={1234567}
trend="+12.3%"
trendPositive
tooltip="Total trading volume over the selected period"
>
<LineChart
  dataPoints={[
    { timestamp: '2024-01-01T00:00:00.000Z', value: 420 },
    { timestamp: '2024-01-02T00:00:00.000Z', value: 510 },
    { timestamp: '2024-01-03T00:00:00.000Z', value: 480 },
    { timestamp: '2024-01-04T00:00:00.000Z', value: 620 },
    { timestamp: '2024-01-05T00:00:00.000Z', value: 590 },
    { timestamp: '2024-01-06T00:00:00.000Z', value: 700 },
    { timestamp: '2024-01-07T00:00:00.000Z', value: 660 },
  ]}
  period="last-week"
  gradient
/>
</MetricCard>

trend is a preformatted string like +12.3%; trendPositive sets its colour and arrow direction. A trend of exactly 0% is hidden. Use tooltip for a one-line explanation of what the number counts. See Choosing a chart for when a line fits.

Axes and legend

Supply vs Borrow

  • Supply
  • Borrow
510ETH
+5.2%
<MetricCard
title="Supply vs Borrow"
numValue={510}
suffix="ETH"
trend="+5.2%"
legend={<ChartLegend labels={['Supply', 'Borrow']} shape="square" />}
>
<VerticalBarChart
  period="last-week"
  series={[
    { label: 'Supply', data: supplyPoints },
    { label: 'Borrow', data: borrowPoints },
  ]}
  xAxis
  yAxis
/>
</MetricCard>

Supply vs Borrow

  • Supply
  • Borrow
510ETH
+5.2%
<MetricCard
title="Supply vs Borrow"
numValue={510}
suffix="ETH"
trend="+5.2%"
legend={<ChartLegend labels={['Supply', 'Borrow']} shape="line" />}
>
<LineChart
  segmentBars={buildSegmentBars(supplyPoints, borrowPoints)}
  segmentConfig={{ labels: ['Supply', 'Borrow'] }}
  period="last-week"
  xAxis
  yAxis
/>
</MetricCard>

Axes and the legend are off by default; turn on only the ones the reader needs. xAxis labels the dates under the chart: every day for a week, every hour for today, and about 5 evenly spaced dates for longer ranges. yAxis adds a value scale on the right with dashed gridlines, in compact numbers such as 1.2k. The bar chart's scale starts at zero; a single-series line chart's scale covers the range of its data. When either axis is on, the card grows from 161px to 200px to fit the labels.

Pass a ChartLegend to the card's legend slot to name each series. It sits in the top-right corner, before any linkLabel button or filter. Use shape="square" with bars and shape="line" with lines, and list the labels in the same order as the chart's series so the colours match. A single series needs no legend; the card title names it.

Active Users

8,421
<MetricCard title="Active Users" numValue={8421} linkLabel="View All" onLinkPress={openUsers} />

linkLabel adds a small bare button with an arrow at the end of the title row; onLinkPress runs when it is pressed. Use it to open the full report behind the number.

Filter

Fees

312ETH
<MetricCard
title="Fees"
numValue={312}
suffix="ETH"
filter={
  <Segments aria-label="Period" defaultValue="7d">
    <Segment value="7d">7D</Segment>
    <Segment value="30d">30D</Segment>
  </Segments>
}
/>

filter renders whatever control you pass at the end of the title row, such as a period switch. The card does not filter anything itself β€” update its value and chart when the control changes.

Preformatted value

Treasury

$1.2M
-0.4%
<MetricCard title="Treasury" value="$1.2M" trend="-0.4%" trendPositive={false} />

Pass value instead of numValue when the number is already formatted, such as an abbreviated currency. numValue is formatted with the reader's locale and can be animated with renderAnimatedValue; value renders as given. Both use tabular numerals, as do the trend and the chart tooltips, so digits do not shift as the value changes.

Loading

Supply vs Borrow

<MetricCard title="Supply vs Borrow" loading>
<VerticalBarChart loading period="last-week" series={[]} />
</MetricCard>

Active Users

<MetricCard title="Active Users" loading>
<LineChart loading period="last-week" />
</MetricCard>

Volume by Chain

<MetricCard title="Volume by Chain" loading>
<HorizontalBarChart loading data={[]} loadingRows={4} />
</MetricCard>

Set loading on the card and on the chart together β€” the card shows a skeleton in place of the value while the chart shows its own placeholder, so the layout does not shift when data arrives. The Vertical Bar Chart draws one placeholder bar per slot in period, so the bar count matches what the data will fill in. The Horizontal Bar Chart draws loadingRows placeholder rows, 5 by default; match it to the number of categories you expect.

When to use

Use MetricCard for a headline number, its trend, and the data behind it charted as a line, bars over time, or a comparison across categories.

Use something else when:

  • The number needs no chart or trend. Set it as text with tabular numerals; see Typography.
  • You are reporting a state, not a quantity. Use Status.
  • The comparison has more than about 8 categories, or readers need exact values side by side. Use a table.

Usage

Do

Constrain the card's width with a wrapper such as w-80, or lay several out on a responsive grid. The chart fills the width it is given.

Don't

Bake an arrow or a colour into trend. Pass the formatted change, like +12.3%, and let trendPositive decide the colour and arrow direction.

Do

Sort Horizontal Bar Chart data before passing it, usually largest first, so the ranking reads top to bottom.

Don't

Draw a Line Chart across categories such as chains or apps. Use a Horizontal Bar Chart; a line implies an order the data doesn't have.

Props

PropTypeDefault
clipContentTypebooleanDefaultfalse
filterTypeReactNodeDefaultβ€”
legend

A ChartLegend, shown in the top-right corner beside any `filter`.

TypeReactNodeDefaultβ€”
linkLabelTypestringDefaultβ€”
loadingTypebooleanDefaultfalse
numValueTypenumberDefaultβ€”
onLinkPressType(() => void)Defaultβ€”
renderAnimatedValueType((value: number) => ReactNode)Defaultβ€”
suffixTypestringDefaultβ€”
titleTypestringDefaultβ€”
tooltipTypestringDefaultβ€”
trendTypestringDefaultβ€”
trendPositiveTypebooleanDefaulttrue
valueTypeReactNodeDefaultβ€”

Vertical Bar Chart

PropTypeDefault
periodrequired

Time range the bars cover. Sets the date format and the number of placeholder bars.

Type"today" | "last-week" | "last-month" | "last-90-days"Defaultβ€”
seriesrequired

One series draws plain bars; two or more stack, first series on top. Points line up by `timestamp`.

TypeVerticalBarSeries[]Defaultβ€”
loading

Show one placeholder bar per slot in `period` while data loads.

TypebooleanDefaultfalse
visibleSeries

Indices of the series to draw. Hidden series drop out of the bars, the scale, and the tooltip. Defaults to all.

Typenumber[]Defaultβ€”
xAxis

Label each bar's date under the chart. Long ranges show about 5 evenly spaced dates.

TypebooleanDefaultfalse
yAxis

Show a value scale on the right with dashed gridlines.

TypebooleanDefaultfalse

Horizontal Bar Chart

PropTypeDefault
datarequired

One bar per category, drawn top to bottom in the order given.

TypeHorizontalBarData[]Defaultβ€”
formatValue

Formats each value in the tooltip and for assistive technology.

Type((value: number) => string)Defaultβ€”
loading

Show placeholder rows while data loads.

TypebooleanDefaultfalse
loadingRows

Number of placeholder rows while `loading` and `data` is empty.

TypenumberDefault5
xAxis

Show a value scale under the bars with dashed gridlines.

TypebooleanDefaultfalse

Line Chart

PropTypeDefault
classNameTypestringDefaultβ€”
dataPointsTypeChartDataPoint[]Defaultβ€”
fadeLeftTypestringDefaultβ€”
formatValueType((raw: string | number) => string)Defaultβ€”
gradientTypebooleanDefaultfalse
loadingTypebooleanDefaultfalse
periodType"today" | "last-week" | "last-month" | "last-90-days"Defaultlast-week
segmentBarsTypeSegmentBarData[]Defaultβ€”
segmentConfigTypeSegmentConfigDefaultβ€”
visibleSeriesIndicesTypenumber[]Defaultβ€”
xAxis

Label dates under the chart. Long ranges show about 5 evenly spaced dates.

TypebooleanDefaultfalse
yAxis

Show a value scale on the right with dashed gridlines.

TypebooleanDefaultfalse

Chart Legend

PropTypeDefault
labelsrequired

Series names, in the same order as the chart's series.

Typereadonly string[]Defaultβ€”
colors

Swatch colour per series. Defaults to the MetricCard chart colours.

Typereadonly string[]Defaultβ€”
shapeType"line" | "square" | nullDefaultsquare

Accessibility

MetricCard renders a plain container, so the title and value read as ordinary text. The optional tooltip uses React Aria's Tooltip on an icon button that carries its own accessible name, and it opens on keyboard focus as well as hover.

The charts are visual summaries: their data points are not exposed to assistive technology. Make sure the headline value and trend carry the takeaway, and offer the underlying data elsewhere, such as a table, when readers need the individual points.

HorizontalBarChart is the exception: it renders a list, and each item reads its category and value, such as "Base: 1,200 ETH". The bars, tooltip, and scale are hidden.

Axis labels are hidden from assistive technology along with the rest of the chart. ChartLegend renders a list, so its series names read as ordinary text.

When the reader prefers reduced motion, the bar charts' tooltips and bar size changes happen at once.

The info button around the title's tooltip icon has a 24px hit area, though the icon itself is 14px.