Skip to main content

Comparison Table

3 min readStableIntermediate

ComparisonTable lays out a feature matrix: one row per feature, one column per option, and a small icon in each cell instead of a wall of yes/no text. Ten cell values render as an icon with a hover tooltip — check, cross, partial, info, star, clock, plus, lock, sparkle, and zap — and any other string renders as plain text. Each example below shows the live component first, then the exact MDX that produced it.

Quick Start

Two required props: headers and rows, both passed as JSON array strings.

FeatureStarterTeam
Custom domainIncludedIncluded
Private access modeNot availableIncluded
app/features/components/comparison-table.mdx
MDX
<ComparisonTable
  headers='["Feature", "Starter", "Team"]'
  rows='[
    { "feature": "Custom domain", "values": ["check", "check"] },
    { "feature": "Private access mode", "values": ["cross", "check"] }
  ]'
/>

Adding Options

title puts a heading bar above the table, and highlight tints one column and prints a Recommended label above its header.

Plan comparison

FeatureStarterRecommendedTeamEnterprise
Custom domainIncludedIncludedIncluded
Private access modeNot availableIncludedIncluded
Audit logNot availablePartialIncluded
app/features/components/comparison-table.mdx
MDX
<ComparisonTable
  title="Plan comparison"
  headers='["Feature", "Starter", "Team", "Enterprise"]'
  highlight={2}
  rows='[
    { "feature": "Custom domain", "values": ["check", "check", "check"] },
    { "feature": "Private access mode", "values": ["cross", "check", "check"] },
    { "feature": "Audit log", "values": ["cross", "partial", "check"] }
  ]'
/>

Advanced

Hover any icon below to read its label; the last row shows a value that matches no indicator key, so it falls through to plain text.

Every indicator key

CapabilityStarterTeamRecommendedEnterprise
Custom domainIncludedIncludedIncluded
Private access modeNot availableIncludedIncluded
Audit logPartialPartialIncluded
Data residencyInfoInfoIncluded
Priority supportNot availablePremiumIncluded
SAML SSOEnterpriseEnterpriseIncluded
Extra editor seatsAdd-onAdd-onIncluded
Scheduled publishingComing soonComing soonComing soon
AI answersNewNewNew
Build concurrencyUpgradeIncludedIncluded
PriceFreePer seatContact sales
app/features/components/comparison-table.mdx
MDX
<ComparisonTable
  title="Every indicator key"
  headers='["Capability", "Starter", "Team", "Enterprise"]'
  highlight={3}
  rows='[
    { "feature": "Custom domain", "values": ["check", "check", "check"] },
    { "feature": "Private access mode", "values": ["cross", "check", "check"] },
    { "feature": "Audit log", "values": ["partial", "partial", "check"] },
    { "feature": "Data residency", "values": ["info", "info", "check"] },
    { "feature": "Priority support", "values": ["cross", "star", "check"] },
    { "feature": "SAML SSO", "values": ["lock", "lock", "check"] },
    { "feature": "Extra editor seats", "values": ["plus", "plus", "check"] },
    { "feature": "Scheduled publishing", "values": ["clock", "clock", "clock"] },
    { "feature": "AI answers", "values": ["sparkle", "sparkle", "sparkle"] },
    { "feature": "Build concurrency", "values": ["zap", "check", "check"] },
    { "feature": "Price", "values": ["Free", "Per seat", "Contact sales"] }
  ]'
/>

headers and rows also accept real JavaScript arrays through a JSX expression. That form skips JSON escaping and reads better once the row list grows past a few entries.

RuntimeNode 20Node 24
SupportedIncludedIncluded
Recommended for new projectsNot availableIncluded
app/features/components/comparison-table.mdx
MDX
<ComparisonTable
  headers={['Runtime', 'Node 20', 'Node 24']}
  rows={[
    { feature: 'Supported', values: ['check', 'check'] },
    { feature: 'Recommended for new projects', values: ['cross', 'check'] },
  ]}
/>

Options

titlestring

Heading text in a bar above the table. Leave it off and the table starts at the header row.

headersJSON string | string[]required

Column labels, as a JSON array string or a real array. The first entry labels the feature column; the rest are the columns being compared. The component renders nothing when this list is empty or the JSON fails to parse.

rowsJSON string | RowData[]required

Row objects, each with a feature label and a values array. Accepts a JSON array string or a real array, and renders nothing when the list is empty or the JSON fails to parse. Each entry in values becomes one cell, so keep the array one shorter than headers; a short array leaves the trailing columns blank and a long one pushes cells past the last header.

highlightnumber

Index into headers of the column to tint. headers[0] is the feature column, so highlight={1} marks the first compared column and adds the Recommended label above it. Passing 0 tints the feature column without the label; an index past the last header tints nothing.

Cell values

Ten keywords render as a colored icon with a tooltip on hover. Matching is exact and lowercase — Check is not check, so it falls through to the plain text branch. Anything that is not one of these ten renders as plain secondary text, which is how you write a price, a quota, or a version number into a cell.

checkcell value

Teal check mark. Tooltip: Included.

crosscell value

Red X. Tooltip: Not available.

partialcell value

Amber minus. Tooltip: Partial.

infocell value

Sky-blue info circle. Tooltip: Info.

starcell value

Purple star. Tooltip: Premium.

clockcell value

Gray clock. Tooltip: Coming soon.

pluscell value

Teal plus. Tooltip: Add-on.

lockcell value

Gray padlock. Tooltip: Enterprise.

sparklecell value

Violet sparkles. Tooltip: New.

zapcell value

Amber lightning bolt. Tooltip: Upgrade.

any other stringcell value

Rendered as plain secondary text, with no icon and no tooltip.

Was this page helpful?