Skip to content

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/react

For 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.

Reference

Propsrows, columns, max, value, formatValue, noneLabel…

Props

CoverageMatrix props
PropType
rowsRequiredreadonly CoverageMatrixRow[]–
columnsRequiredreadonly CoverageMatrixColumn[]–
maxnumber

The value of a full cell. Default: the largest value.

–
valuestring | null

The selected row's id, controlled; null for none.

–
defaultValuestring | null

The row selected at first.

default null

null
onValueChange(id: string) => void

Called with the row's id when a row is selected.

–
formatValue(value: number) => string

Formats a covered cell's value in its tooltip and spoken name: "3 rules". Default: the locale's number format.

–
noneLabelstring

What a cell with nothing says, and the key's name for the hatch. Default: the labels' "None".

–
depthLabelstring

The key's name for a filled cell. Default: the labels' "Fill shows depth".

–
legendboolean

The key under the grid.

default true

true
localestring

Default: the YoujProvider locale.

–
labelstring

Accessible name of the grid. Default: the labels' "Coverage matrix".

–
configReadonly<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

Motion tokens
MomentTokens
Revealthe fills rise from the bottom of their cells, row after row, when a third of the grid scrolls into view
duration.slowease.emphasized

340ms · 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.emphasized

340ms · 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.standard

140ms · 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 (your label, 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", or noneLabel), 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.