Skip to content
microcharts
ReferenceDecisionCohortTriangle

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.

CohortTriangle
interactive · 5.32 kB · static · 2.7 kB

Install

Import & usereact/cohort-triangle
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] },];
Needs package + stylesheetSet up with AI

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.

highlight a vintage vs dense, no labels

The unit prop renames the age columns in the summary and announcements — "month", "week", "day", whatever the cohort period is.

Edge cases

single cohort
missing reading (gap, not zero)
0–100 input
locale

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

PropTypeNotes
data*{ label, values }[]One row per cohort; values[i] = retention at age i (0–1 or 0–100, ragged).
labelsbooleanCohort labels in a left gutter (default true; drops at tiny cell sizes).
highlightstringRing the cohort with this label — the comparison focus.
unitstringAge-column noun for the summary (default "period").
cellnumberCell edge length in viewBox units.
titlestringAccessible name; joins the auto summary.
summarystring | falseOverride or disable the auto summary.
gapnumberGap between cells in viewBox units (default 2).
animatebooleaninteractiveOpt-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.

Related charts