Comparison
betaA table that compares your product with other ways of doing the job, with a dot, a ring, or a dash for included, manual, and missing.
Preview
| Team task | Workspace | Shared table | Group chat |
|---|---|---|---|
| Who's coming, with an answer-by date | Included | Manual work | Manual work |
| The number going, per event | Included | Manual work | Manual work |
| Reminders to whoever hasn't answered | Included | Not included | Not included |
| A calendar on everyone's phone | Included | Not included | Not included |
| Team and admin roles | Included | Manual work | Manual work |
| Goals by team | Included | Manual work | Manual work |
| Members download their own data | Included | Not included | Not included |
two columns, your own legend words
| Feature | Pro | Free |
|---|---|---|
| Who's coming, with an answer-by date | Yes | Limited |
| The number going, per event | Yes | Limited |
| Reminders to whoever hasn't answered | Yes | No |
narrow container: the table scrolls sideways
| Team task | Workspace | Shared table | Group chat |
|---|---|---|---|
| Who's coming, with an answer-by date | Included | Manual work | Manual work |
| The number going, per event | Included | Manual work | Manual work |
| Reminders to whoever hasn't answered | Included | Not included | Not included |
| A calendar on everyone's phone | Included | Not included | Not included |
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the website component. It also adds every component it uses. Its own files go in your
componentsfolder, and they are yours to edit.npx shadcn@latest add @txbrendan/comparison
Use it for
- A landing or pricing page, to show what people get with you and what they do by hand without you.
- Two to four columns. Set `highlight` on your own, so it sits on a white panel.
- Name the jobs people do in each row, not your feature names: "Reminders to whoever hasn't answered".
- Explain what partial means in `note`.
Avoid it for
- Naming a competitor's product unless your claims about it are true and current. Compare ways of working instead.
- Prices or numbers in cells. Use a Table, or Pricing.
- More than about twelve rows. Keep the ones that decide the choice.
Props
- columns
The products or ways of working, left to right. Labels must be unique, since each one is a key.
{ label: string; icon?: ReactNode; highlight?: boolean }[]
- rows
The jobs, top to bottom, with one value per column. A missing value reads as no. Labels must be unique.
{ label: string; icon?: ReactNode; values: ("yes" | "partial" | "no")[] }[]
- rowsLabel
The heading over the row labels.
string · default "Feature"
- legend
The words for each mark, in the legend and for screen readers.
{ yes: string; partial: string; no: string } · default { yes: "Included", partial: "Manual work", no: "Not included" }
- note
A small line under the table.
ReactNode
- label
The name screen readers give the table's box. Give each table on a page its own.
string · default "Comparison"
- className
Extra classes on the root, which holds the container query.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| columns | { label: string; icon?: ReactNode; highlight?: boolean }[] | - | The products or ways of working, left to right. Labels must be unique, since each one is a key. |
| rows | { label: string; icon?: ReactNode; values: ("yes" | "partial" | "no")[] }[] | - | The jobs, top to bottom, with one value per column. A missing value reads as no. Labels must be unique. |
| rowsLabel | string | "Feature" | The heading over the row labels. |
| legend | { yes: string; partial: string; no: string } | { yes: "Included", partial: "Manual work", no: "Not included" } | The words for each mark, in the legend and for screen readers. |
| note | ReactNode | - | A small line under the table. |
| label | string | "Comparison" | The name screen readers give the table's box. Give each table on a page its own. |
| className | string | - | Extra classes on the root, which holds the container query. |
Accessibility
- It is a real table, with column and row headers, so screen readers announce each cell's row and column.
- Each mark is aria-hidden, and its legend word sits beside it as screen reader text.
- The marks differ by shape as well as color: a filled dot, a ring, and a red dash.
- The marks use each tone's text color, which every theme must pass at 4.5:1 on muted and card.
- On a narrow screen the table scrolls sideways. Its box is a named region that takes focus, so the keyboard can scroll it.
- In forced colors the gray panel gets a border, so its edge still shows when its fill is painted away.
- In forced colors the dot and dash take the text color, and your highlighted column is outlined instead of sitting on a white panel.
Examples
You against doing it by hand
<Comparison
rowsLabel="Team task"
columns={[
{ label: "Workspace", highlight: true },
{ label: "Shared table" },
{ label: "Group chat" },
]}
rows={[
{ label: "Who's coming", values: ["yes", "partial", "partial"] },
{ label: "Reminder emails", values: ["yes", "no", "no"] },
]}
note="Manual work means the team keeps the record itself."
/>