Comparison Matrix
A plan-feature matrix with accessible availability cells and a highlighted plan column.
- Status
- Since
0.3.0- Accessibility pattern
- native table semantics
Last updated
Install
Add Comparison Matrix from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/comparison-matrixThe same command installs the registry items it composes: @vegastack/use-overflow.
Usage
import {
ComparisonGroup,
ComparisonMatrix,
ComparisonRow,
} from "@/components/ui/comparison-matrix";
<ComparisonMatrix plans={["Free", "Pro"]} highlightedIndex={1}>
<ComparisonGroup>Enrichment</ComparisonGroup>
<ComparisonRow feature="Company data" availability={[true, true]} />
<ComparisonRow feature="Seats" availability={["3", "Unlimited"]} />
</ComparisonMatrix>;Anatomy
Comparison Matrix is a compound component. Every exported part, with the
data-slot it renders (generated from the canonical source):
Examples
The highlighted plan uses a subtle column surface rather than elevation. Boolean availability pairs a visible glyph with screen-reader copy; literal limits render as authored.
At narrow widths the matrix keeps readable columns and scrolls horizontally. The feature column is sticky, the scroll region is labelled and becomes keyboard-focusable exactly while it can scroll, and edge fades indicate that more plans are available off-screen.
Unknown cells and no promoted plan
highlightedIndex is optional — omit it and no column is promoted. When a row's availability
array is shorter than plans, the matrix holds every column open and marks the missing cells
"Not specified" (overridable per row with unknownLabel) rather than shifting the row left or
silently reading as "not included". An unsupplied value is not a negative claim.
API Reference
ComparisonMatrix
| Prop | Type | Default | Description |
|---|---|---|---|
plans* | React.ReactNode[] | — | Plan names, in column order. |
highlightedIndex | number | — | Index into plans to tint as the promoted column. |
planActions | React.ReactNode[] | — | Optional per-plan header extras (CTAs), aligned under the names. |
scrollLabel | string | 'Plan comparison table' | Accessible label for the horizontally scrollable region. |
Data attributes and CSS variables on ComparisonMatrix
| Attribute | Values |
|---|---|
data-highlighted | "" |
data-scrollable | "" |
data-slot | "comparison-matrix" | "comparison-matrix-container" |
ComparisonGroup
ComparisonGroup passes native <tr> props and a forwarded ref to its group row.
ComparisonRow
| Prop | Type | Default | Description |
|---|---|---|---|
availability* | React.ReactNode[] | — | One entry per plan: true (included → ✓), false (not included → −), or
any node for a literal value ("3 seats"). |
feature* | React.ReactNode | — | The feature name (the row header). |
includedLabel | string | 'Included' | Screen-reader copy for included boolean cells. |
notIncludedLabel | string | 'Not included' | Screen-reader copy for unavailable boolean cells. |
unknownLabel | string | 'Not specified' | Screen-reader copy for a cell with no supplied value (fewer availability entries than
plans). |
Data attributes and CSS variables on ComparisonRow
| Attribute | Values |
|---|---|
data-slot | "comparison-row" |
Accessibility
Native table semantics: plans are columnheaders, features are rowheaders, groups span the
table. Boolean availability renders ✓/− glyphs WITH sr-only text ("Included"/"Not
included") — never bare dots. Literal values ("3 seats") render as-is. The enclosing region uses
scrollLabel ("Plan comparison table" by default) as its accessible name.
| Key | Action |
|---|---|
| Tab | Move through interactive plan actions in source order. |
| Shift + mouse wheel | Scroll horizontally where the platform supports it. |
| Enter / Space | Activate the focused plan action. |
| Contract | States tested |
|---|---|
| Behaviour | default, success, scrollable |
| Accessibility | semantic-html |
| Visual | default, success |