Deck
Browse the catalog

Build / Molecules

BreakdownList

  • molecule
  • play-fn

A ranked breakdown: one row per value of a dimension, each with a bar showing how it compares.

This is the workhorse of the analytics screen — traffic sources, locations, devices, browsers, link performance all answer the same question, so they are one component rather than five layouts.

The figure is written beside every row, so the bar is a second reading of a number that is already there rather than the only way to get it. That also makes the list its own table view: nothing is locked inside the chart.

Rows are rendered in the order given. Sorting is the caller's call, because "top locations" and "the funnel in order" are both legitimate.

import { BreakdownList } from '@meetcard/deck'

Example

<BreakdownList label="Where engagement comes from" items={[ { label: 'QR', value: 631, meta: '37%' }, { label: 'LinkedIn', value: 388, meta: '23%' }, ]} />

Stories

Every preview below is the real component, rendered in this page from the same story Storybook runs. The width control resizes each preview's container; CSS media queries still follow the browser window, so breakpoint behaviour is worth checking in Storybook.

Default
Source
const Default = () => <Card style={{ maxWidth: 420 }}>
    <BreakdownList
        label="Where engagement comes from"
        items={[
          { label: 'QR', value: 631, meta: '37%' },
          { label: 'LinkedIn', value: 388, meta: '23%' },
          { label: 'Link', value: 273, meta: '16%' },
          { label: 'Email', value: 222, meta: '13%' },
          { label: 'Direct', value: 191, meta: '11%' },
        ]} />
</Card>;

Scaled to the sum instead of the leader, so a bar's length is its share of the whole. Only honest when the rows genuinely partition something.

Share Of Total
Source
const ShareOfTotal = () => <Card style={{ maxWidth: 420 }}>
    <BreakdownList
        label="Devices"
        items={[
          { label: 'Mobile', value: 1288, meta: '76%', icon: <Smartphone /> },
          { label: 'Desktop', value: 318, meta: '19%', icon: <Laptop /> },
          { label: 'Tablet', value: 78, meta: '5%', icon: <Tablet /> },
        ]}
        scale="total" />
</Card>;

A leading mark helps when the labels are a category people scan for.

With Icons
Source
const WithIcons = () => <Card style={{ maxWidth: 420 }}>
    <BreakdownList
        label="Top locations"
        items={[
          { label: 'United States', value: 1067, meta: '63%', icon: <Globe /> },
          { label: 'Canada', value: 218, meta: '13%', icon: <Globe /> },
          { label: 'United Kingdom', value: 172, meta: '10%', icon: <Globe /> },
          { label: 'Germany', value: 121, meta: '7%', icon: <Globe /> },
          { label: 'Other', value: 106, meta: '6%', icon: <Globe /> },
        ]} />
</Card>;

The figure can carry its own unit when the number alone is ambiguous.

Custom Value Labels
Source
const CustomValueLabels = () => <Card style={{ maxWidth: 420 }}>
    <BreakdownList
        label="Link performance"
        items={[
          { label: 'Book a time', value: 298, valueLabel: '298 clicks', meta: '17% CTR' },
          { label: 'Save contact', value: 254, valueLabel: '254 clicks', meta: '15% CTR' },
          { label: 'LinkedIn profile', value: 128, valueLabel: '128 clicks', meta: '8% CTR' },
          { label: 'Company site', value: 62, valueLabel: '62 clicks', meta: '4% CTR' },
        ]} />
</Card>;

A card shared today, nothing measured yet. Flat bars are the honest rendering — the rows exist, the numbers are genuinely zero.

Nothing Measured Yet
Source
const NothingMeasuredYet = () => <Card style={{ maxWidth: 420 }}>
    <BreakdownList
        label="Where engagement comes from"
        items={[
          { label: 'QR', value: 0, meta: '0%' },
          { label: 'LinkedIn', value: 0, meta: '0%' },
          { label: 'Link', value: 0, meta: '0%' },
        ]} />
</Card>;

Long labels truncate rather than pushing the figures off the row.

Long Labels
Source
const LongLabels = () => <Card style={{ maxWidth: 420 }}>
    <BreakdownList
        label="Where engagement comes from"
        items={[
          { label: 'Conference badge QR code — RevOps Summit', value: 631, meta: '37%' },
          { label: 'LinkedIn profile featured section', value: 388, meta: '23%' },
          { label: 'Direct', value: 88, meta: '5%' },
        ]} />
</Card>;

Props

PropTypeDefaultDescription
itemsrequiredBreakdownItem[]—Rows, in the order they should read. Not sorted for you.
labelrequiredstring—Names the list for assistive tech, e.g. "Where engagement comes from".
formatValue(value: number) => string(value: number) => value.toLocaleString()Formats value. Defaults to the viewer's locale grouping.
scale'max' | 'total''max'What the bars are drawn against. max scales to the largest row, so the leader always fills the track and differences between the rest are easy to see. total scales to the sum, so a bar's length is its share of the whole. Use total only when the rows genuinely partition something.

Also accepts everything from Omit, so id, data-* and event handlers pass straight through.

Open in Storybookfor controls, the a11y panel and the interaction runner.