CohortTriangle
CohortTriangle shades retention by vintage and age so you can compare every cohort at the same maturity.
- encodes
- color intensity (discrete levels) over an age × cohort grid
- precision
- low — steer to a table when exact per-cell values matter
- nodes
- 1 per cell
At month 1, the March vintage retains worst: 47%. Reading down a column compares every cohort at the same maturity, which is the question this chart is for. CohortTriangle stacks cohorts as rows and ages as columns, shading each cell by a discrete retention level. Newer vintages have been observed for fewer ages, which gives the block its triangle shape.
Install
import { CohortTriangle } from "@microcharts/react/cohort-triangle";// cohorts — real values under “Sample data” below<CohortTriangle data={cohorts} unit="month" title="Monthly retention" />Sample data
// Five monthly vintages, decaying then flattening — ragged because newer// cohorts have been observed for fewer months. March leaks worst by month 1.const cohorts = [ { label: "Jan", values: [1, 0.62, 0.48, 0.41, 0.38, 0.37] }, { label: "Feb", values: [1, 0.58, 0.44, 0.38, 0.35] }, { label: "Mar", values: [1, 0.47, 0.36, 0.31] }, { label: "Apr", values: [1, 0.55, 0.42] }, { label: "May", values: [1, 0.52] },];Try it
When to use it
Use it for monthly or weekly cohorts side by side, spotting which vintage decays worst, or reading retention at equal maturity in a KPI card. Color intensity is deliberately approximate, so pair it with a number when you need exact per-cell values. For a single cohort, RetentionCurve draws the decay directly.
Sizing
CohortTriangle sizes from cell — the edge length of one square in viewBox units. Bump it to scale the whole grid; the
viewBox keeps the aspect ratio when CSS drives the width. Row labels seat automatically and drop out cleanly below
roughly an 8-unit cell.
Variants
Rows are cohorts in input order and columns are age. Row labels are on by default; turn them off for a denser block, or
highlight one vintage to anchor the equal-maturity comparison. No worst-vintage flag is painted onto the grid — the
summary names the laggard in words instead.
The unit prop renames the age columns in the summary and announcements — "month", "week", "day", whatever the
cohort period is.
Edge cases
A single cohort skips the comparison and states its first reading. A null (or any non-finite) value renders as an
outlined gap slot, a "measured nothing here" cell, never a shaded 0%. data accepts either a 0–1 fraction or a
0–100 percent series, whichever the max value implies, so a raw percent export renders identically to its fraction form.
With a locale, every announced number follows that locale's own formatting.
Four homes
Accessibility
Intensity is quantized to five discrete levels rather than a continuous ramp, since a smooth gradient would imply a precision a handful of pixels cannot deliver. It is still a color channel, so CohortTriangle always pairs it with a numeric summary that compares at equal maturity: "5 cohorts; at month 1, Mar retains worst (47%); newest May starts at 100%.". Direction is stated in words, never by color alone, so it survives forced-colors and color-blind viewing. The interactive entry adds 2-D arrow-key navigation, announcing each cell as "Feb cohort, month 1: 58%" as you move.
The interactive entry follows the shared interaction contract:
arrow keys rove between units on both axes, Home and End jump to the ends, and a click, tap, Enter or
Space selects a unit — pinning its readout so it survives blur, until you select it again or press Escape.
On touch, a tap pins and a drag scrubs.
Props
| Prop | Type | Notes |
|---|---|---|
| data* | { label, values }[] | One row per cohort; values[i] = retention at age i (0–1 or 0–100, ragged). |
| labels | boolean | Cohort labels in a left gutter (default true; drops at tiny cell sizes). |
| highlight | string | Ring the cohort with this label — the comparison focus. |
| unit | string | Age-column noun for the summary (default "period"). |
| cell | number | Cell edge length in viewBox units. |
| title | string | Accessible name; joins the auto summary. |
| summary | string | false | Override or disable the auto summary. |
| gap | number | Gap between cells in viewBox units (default 2). |
| animate | boolean | interactiveOpt-in entrance motion when the chart mounts client-side — add import "@microcharts/react/motion" once. Inert on the server, on hydrated server HTML, and under prefers-reduced-motion. |
Plus the shared grammar — data, domain, color, title, summary, format — and the layout props (width, height, className, style) that every chart accepts. Interactive entries also share animate and live, and — wherever a chart has more than one navigable unit — onActive, onSelect, selectedIndex and defaultSelectedIndex; and — wherever the chart shows a hover value — readout. See the shared grammar.