Skip to content
txbrendan/ui

Comparison

beta

A 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

  • Included
  • Manual work
  • Not included
Team taskWorkspaceShared tableGroup chat
Who's coming, with an answer-by dateIncludedManual workManual work
The number going, per eventIncludedManual workManual work
Reminders to whoever hasn't answeredIncludedNot includedNot included
A calendar on everyone's phoneIncludedNot includedNot included
Team and admin rolesIncludedManual workManual work
Goals by teamIncludedManual workManual work
Members download their own dataIncludedNot includedNot included

A basic shared table and group chat, with no add-ons. Manual work means the team keeps the record itself.

two columns, your own legend words

  • Yes
  • Limited
  • No
FeatureProFree
Who's coming, with an answer-by dateYesLimited
The number going, per eventYesLimited
Reminders to whoever hasn't answeredYesNo

narrow container: the table scrolls sideways

  • Included
  • Manual work
  • Not included
Team taskWorkspaceShared tableGroup chat
Who's coming, with an answer-by dateIncludedManual workManual work
The number going, per eventIncludedManual workManual work
Reminders to whoever hasn't answeredIncludedNot includedNot included
A calendar on everyone's phoneIncludedNot includedNot included

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the website component. It also adds every component it uses. Its own files go in your components folder, 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

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."
/>