Coverage Matrix
Show how deep each row is covered by each column, with gaps hatched and a selectable row whose gaps turn red.
import { CoverageMatrix, CoverageMatrixColumn, CoverageMatrixRow } from '@youj/react'
Installation & usage
npm i @youj/reactFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Use a coverage matrix when the question is "where are the gaps": detection rules by tactic and source, features by platform, tests by module and browser, translations by page and language. A cell's fill is how deep the coverage goes, so thin spots read as well as empty ones. For who may do what, use a Level Matrix; for one value over a calendar, a Calendar Heatmap. The Coverage grid card is built on it.
import { CoverageMatrix } from '@youj/react'
<CoverageMatrix
label="Detection coverage"
columns={[
{ id: 'identity', label: 'Identity', tone: 'chart-3' },
{ id: 'network', label: 'Network', tone: 'chart-2' },
]}
rows={[
{ id: 'access', label: 'Initial access', values: [4, 2] },
{ id: 'lateral', label: 'Lateral movement', values: [1, 0] },
]}
defaultValue="lateral"
formatValue={(n) => `${n} rules`}
noneLabel="No rules"
/>Each row gives one value per column, in the order of columns; 0 or null is no coverage and draws the hatch. A cell fills from the bottom by its value out of max (by default the largest value, one scale for every cell), never less than a sliver when it has any. Columns take the chart colors in order unless they set a tone. The selected row is value/defaultValue/onValueChange; in it the gaps take the danger tone, so it's the row your suggestion or action is about. formatValue, noneLabel and depthLabel word the cells and the key; legend={false} drops the key. No rows or columns shows the empty state.
Description
A made-up app's features against the platforms they ship on. Each cell fills by how complete the feature is there, and a feature that hasn't started on a platform is hatched. Pick a row (click it, or Enter on one of its cells) and its gaps turn red; the line under it reacts to the pick.
Code
coverage-matrix-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propsrows, columns, max, value, formatValue, noneLabel…
Props
| Prop | Type | |
|---|---|---|
rowsRequired | readonly CoverageMatrixRow[] | – |
columnsRequired | readonly CoverageMatrixColumn[] | – |
max | numberThe value of a full cell. Default: the largest value. | – |
value | string | nullThe selected row's id, controlled; | – |
defaultValue | string | nullThe row selected at first. default null | null |
onValueChange | (id: string) => voidCalled with the row's id when a row is selected. | – |
formatValue | (value: number) => stringFormats a covered cell's value in its tooltip and spoken name: "3 rules". Default: the locale's number format. | – |
noneLabel | stringWhat a cell with nothing says, and the key's name for the hatch. Default: the labels' "None". | – |
depthLabel | stringThe key's name for a filled cell. Default: the labels' "Fill shows depth". | – |
legend | booleanThe key under the grid. default true | true |
locale | stringDefault: the YoujProvider locale. | – |
label | stringAccessible name of the grid. Default: the labels' "Coverage matrix". | – |
config | Readonly<Record<string, { color?: string }>>A ChartConfig shared with your other charts: a column whose id is a key takes its color, so one series matches across chart types. | – |
Other props pass through: extends ComponentProps<'div'>. | ||
StylingParts and data attributes
Styling
Parts: coverage-matrix, coverage-matrix-grid, coverage-matrix-columns, coverage-matrix-row, coverage-matrix-label, coverage-matrix-cell, coverage-matrix-fill, coverage-matrix-legend, coverage-matrix-tooltip, coverage-matrix-summary and coverage-matrix-empty. A row has data-state="selected" when it is chosen; a cell has data-state (covered or none). The cell size is --coverage-matrix-cell (32px).
MotionReveal, new values · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Revealthe fills rise from the bottom of their cells, row after row, when a third of the grid scrolls into view | duration.slowease.emphasized340ms · cubic-bezier(0.6, 0, 0.1, 1) | 340mscubic-bezier(0.6, 0, 0.1, 1) | |
| New valuesa fill eases to its new depth | duration.slowease.emphasized340ms · cubic-bezier(0.6, 0, 0.1, 1) | 340mscubic-bezier(0.6, 0, 0.1, 1) | |
| Selecting a rowthe row band and its gaps change color | duration.fastease.standard140ms · cubic-bezier(0.2, 0, 0, 1) | 140mscubic-bezier(0.2, 0, 0, 1) |
Reduced motion
Drawn in place; changes land at once.
Reduced motion
The fills are drawn in place, and new values land at once.
AccessibilityKeyboard, focus, screen readers
Accessibility
- A named
grid(yourlabel, or the labels' "Coverage matrix") described by a summary of how many cells are covered, of how many. - A row per row, its header the row's name, and the chosen one
aria-selected. Every cell is named with its column and value ("Cloud: 3 rules", ornoneLabel), so the decorative column names over the grid aren't needed to read it. - One tab stop. The arrows move between the row names and the cells (mirrored in RTL), Home and End go to a row's name and its last cell, and Enter or Space selects the row. Focus shows the cell's tooltip, as hovering does.
- A gap is hatched and dashed as well as colored, and its value is spoken, so color is never the only cue.
- Its built-in text comes from the labels, so
<YoujProvider labels>translates it.