Skip to content

CapTableSnapshotCard

Hero example

When to use

Reach for CapTableSnapshotCard when the canonical question is "what does the cap table look like right now?" — surfaced as an agent reply to "show me ownership," in the equity-dashboard hero row, or as a static snapshot embedded in an investor update. The card reads from the live CapTable view; the syncedAgo prop carries the freshness signal so readers know whether the percentages reflect the most recent grant or are stale.

Don't reach for CapTableSnapshotCard for proposed (not yet approved) changes — those belong in OptionGrantCard for grants or in dedicated round-modeling surfaces for financings. Don't use it for a single-holder query either — for "what does Jane own?" the right surface is a focused detail page, not the full donut.

Do — render CapTableSnapshotCard when an agent says "Here's the cap table after the seed close — Jane 38.2%, Mike 24.1%."
Don't — render it with forward-projected percentages. Use a round-modeling surface; the donut implies live state.

Anatomy

Three structural regions stacked top-to-bottom:

  1. Header. Chart icon + "Cap Table — Live" + right-aligned Synced {syncedAgo} (default 12s ago).
  2. Body grid (2 columns).
    • Donut. 180px-wide SVG. Background ring in --ink-5, segments colored per CapTableRow.color, centre text reads FDS / total (default 10.0M). Role on the donut is "img" with aria-label="Cap-table donut".
    • Holder list. One row per CapTableRow. Row layout: 9px square swatch + name + role + right-aligned percentage in mono with tabular numerals.
  3. Footer. Primary Open Cap Table + secondary Export to Excel.

The donut math: total circumference 2π × 38, each segment computes its dash array from pct, segments are drawn clockwise from 12 o'clock via a -90° rotation.

Props

PropTypeDefaultDescription
rowsCapTableRow[]DEFAULT_ROWS
fdsLabelstring"10.0M"
syncedAgostring"12s ago"

The default rows set illustrates a typical seed-stage cap table (Jane, Mike, Silverstine, Goldilocks, Option Pool, Other) so the design-site preview renders meaningfully without a consumer wiring up data. In production, pass real rows derived from the CapTable view.

States

The card is single-state by design — it reflects a snapshot. The only variation is row count.

Row countBehaviour
1–8 rows (typical)Renders inline; no scroll.
9+ rowsHolder list scrolls inside the card body. Donut stays full size.
0 rowsRenders the default 6-row demo set. Don't ship the default to users — handle the empty case in the wrapping screen.

Density

In compact, the body grid drops to a single column (donut above list) at viewport widths < 480px. Row vertical padding drops from 8 to 4px. The donut stays 180px (legibility floor).

Themes

In dark, the donut background ring reads --ink-5 (which adapts to the dark surface) and segment colors retain their hue. Forced-colors collapses segments to alternating Highlight / Canvas stripes — semantic colors don't survive but ordering does.

Tokens consumed

No tokens match. No tokens match.

Default segment colors pull from the token surface — --action-create (founder green), --action-mutate (purple), --alert-dot (red), --status-sage (sage), --gold-warm (option pool), --ink-18 (other). Consumers can override any of them via CapTableRow.color.

Accessibility

  • Keyboard interactions. Tab order: Open Cap TableExport to Excel. The donut and holder list are not focusable.
  • ARIA roles and properties. The donut SVG carries role="img" and aria-label="Cap-table donut". Decorative swatches in the list are aria-hidden.
  • Focus order. Footer only.
  • Screen-reader expectations. Reading order: "Cap Table — Live, Synced {time ago}", "Cap-table donut" (alt), then each holder row as "{name}, {role}, {percentage}%".
  • Reduced motion. No keyframes in this card. Even if you add a mount animation, the canonical motion contract requires it to collapse to opacity under prefers-reduced-motion: reduce.
  • Forced colors. Donut segments → alternating Highlight / Canvas. Swatches → ButtonText filled squares.
  • WIG rules. Tabular numerals on percentages (WIG typography). SVG carries role="img" and aria-label (WIG image rule). Color is never the sole differentiator — every segment has both color and a labelled list row, so colour-blind readers still get the data.

Do / Don't

Do — pre-sort rows by pct descending so the donut and list read in the same order.
Don't — pass overlapping percentages summing to more than 100. The donut will draw past the ring.
Do — pass a meaningful syncedAgo (e.g. "12s ago", "5m ago"). It's the user's freshness signal.
Don't — pass an absolute timestamp. "2026-05-12T10:53:04Z" is not the question the user is asking.
Do — collapse small holders into "Other (N)" rows above ~8 visible holders. The donut tail becomes illegible past that.
Don't — render every individual angel investor. Group below a 1% threshold.

Recipes

This card appears in:

Code example

import { CapTableSnapshotCard } from "@matter/components";

export function CapTableSnapshotInline({ entity }: { entity: Entity }) {
  return (
    <CapTableSnapshotCard
      rows={entity.cap_table.holders.map((h) => ({
        name: h.display_name,
        role: h.role,
        pct: h.fds_percent,
        color: h.brand_color,
      }))}
      fdsLabel={entity.cap_table.fds_display}
      syncedAgo={entity.cap_table.synced_relative}
    />
  );
}

Source

packages/components/src/InlineCard/CapTableSnapshotCard

On this page