Retention Cohort

card-ui table-ui badge-ui

Composition pattern: render a retention cohort "heatmap" using a regular table-ui with a badge-based cell renderer. Each cell is a badge-ui variant tinted by the retention bucket, so the table still sorts, paginates, and copies to clipboard like any other data table.

When to use

A true pixel-grid heatmap loses the numeric precision that retention analysis needs — you want to see that W4 held at 54% AND scan the color band. A table with badge cells gives you both: precise numbers on one axis, color-coded buckets across the row. It also preserves accessibility (each number is a real table cell) and lets the same component render partial cohorts (null cells where the cohort hasn't reached that week yet).

Default retention table

Each week-column uses the same formatPct renderer, which wraps the number in a badge-ui whose variant is keyed to three buckets: ≥60% success, 30–60% warning, <30% muted. Null values render blank so partial cohorts don't force fake zeros.

Retention by signup week

% of users still active in week N

With a legend

For first-time viewers, pair the table with a tiny legend in the card footer so the buckets are decoded. The legend itself is three badge-uis — same component the cells use — keeping the visual vocabulary tight.

Retention by signup week

Percent of the cohort still active N weeks after signup

With headline stats

When the cohort table is the only chart in a section, prepend a stat pair for the most important numbers — W4 (early signal) and W12 (sustained retention). Stops users having to scan every row to answer "is this healthy?"

Retention

New signups · rolling 6-week view

The cell renderer

All three examples use the same formatPct function defined in the page's setup.js. The column-definition object references it via render: formatPct:

const formatPct = (v) => { if (v == null) return ''; const n = Math.round(v); const variant = n >= 60 ? 'success' : n >= 30 ? 'warning' : 'muted'; return `<badge-ui text='${n}%' variant='${variant}'></badge-ui>`; }; const columns = [ { key: 'week', label: 'Signup week' }, { key: 'size', label: 'Users' }, { key: 'w1', label: 'W1', render: formatPct }, { key: 'w2', label: 'W2', render: formatPct }, { key: 'w4', label: 'W4', render: formatPct }, { key: 'w8', label: 'W8', render: formatPct }, { key: 'w12', label: 'W12', render: formatPct }, ];

Variants & knobs

Anti-patterns

Authoring rules