Back to Widget Patterns
v2.5Interactive Tooling

Comparison Tool

Feature comparison table: the host product against 2 to 3 competitors. Boolean, text, and rating value types. Optional category toggles and a highlighted column. Composes PrimaryButtonCTA at the footer.

Live preview

Rendered with real component code. Each example demonstrates a documented variant.

Three-way comparison

How we compare

A side-by-side look at the features that matter most.

Our product (Our pick)Acme SuiteGlobex Pro
Free trialNo credit card required
Custom domain
API accessFull REST + webhooksRead-only RESTREST only
Support24/7 live chatEmail (48 h)Tickets only
Ease of setup
Analytics depth

With category toggles

How we compare

Expand each category to explore specific features.

Our product (Our pick)Acme SuiteGlobex Pro
Free trialNo credit card required
Custom domain

React props

Configure the component via props in React or via attributes in HTML.

PropTypeDefaultDescription
competitorsCompetitor[]requiredOrdered list of column definitions; set highlighted: true on your product to apply the accent column style
featuresFeatureRow[]requiredOrdered list of feature rows; each row carries a values map keyed by competitor name
titlestringundefinedOptional heading centered above the table
descriptionstringundefinedOptional supporting copy shown below the title
showCategoryTogglesbooleanfalseWhen true, groups rows by the category field and renders collapsible category header rows
defaultExpandedCategoriesstring[][]Category names that are expanded on initial render when showCategoryToggles is true
finalCta{ label: string; href: string }undefinedRenders a centered PrimaryButtonCTA below the table when provided
classNamestringundefinedExtra class applied to the root section element

HTML usage

The HTML variant uses the same shared styles and class names. Drop into any stack.

<link rel="stylesheet" href="path/to/comparison-tool-vs-competitors/styles.css">

<section class="cmp">
  <header class="cmp__header">
    <h2 class="cmp__title">How we compare</h2>
    <p class="cmp__description">A side-by-side look at the features that matter most.</p>
  </header>

  <div class="cmp__table-wrap">
    <table class="cmp__table">
      <thead>
        <tr>
          <th scope="col" class="cmp__col-head"></th>
          <!-- Mark your product column with cmp__col-head--highlighted -->
          <th scope="col" class="cmp__col-head cmp__col-head--highlighted">Our product</th>
          <th scope="col" class="cmp__col-head">Acme Suite</th>
          <th scope="col" class="cmp__col-head">Globex Pro</th>
        </tr>
      </thead>
      <tbody>
        <!-- Boolean row -->
        <tr>
          <th scope="row" class="cmp__row-head">
            Free trial
            <span class="cmp__row-description">No credit card required</span>
          </th>
          <!-- highlighted cell -->
          <td class="cmp__cell cmp__cell--highlighted">
            <span aria-label="Yes">
              <svg class="cmp__icon" viewBox="0 0 20 20" fill="none" aria-hidden="true">
                <circle cx="10" cy="10" r="9" fill="#1e5fcf" opacity="0.12"/>
                <path d="M6 10.5l3 3 5-5" stroke="#1e5fcf" stroke-width="1.75"
                      stroke-linecap="round" stroke-linejoin="round"/>
              </svg>
            </span>
          </td>
          <td class="cmp__cell">
            <span aria-label="Yes"><!-- same check SVG --></span>
          </td>
          <td class="cmp__cell">
            <span aria-label="No">
              <svg class="cmp__icon" viewBox="0 0 20 20" fill="none" aria-hidden="true">
                <circle cx="10" cy="10" r="9" fill="rgba(16,37,66,0.3)" opacity="0.08"/>
                <path d="M7 7l6 6M13 7l-6 6" stroke="rgba(16,37,66,0.3)"
                      stroke-width="1.75" stroke-linecap="round"/>
              </svg>
            </span>
          </td>
        </tr>

        <!-- Text row -->
        <tr>
          <th scope="row" class="cmp__row-head">API access</th>
          <td class="cmp__cell cmp__cell--highlighted">Full REST + webhooks</td>
          <td class="cmp__cell">Read-only REST</td>
          <td class="cmp__cell">REST only</td>
        </tr>

        <!-- Rating row (5 pips) -->
        <tr>
          <th scope="row" class="cmp__row-head">Ease of setup</th>
          <td class="cmp__cell cmp__cell--highlighted">
            <span class="cmp__rating" aria-label="5 out of 5">
              <!-- 5 filled SVG circles using cmp__rating-pip class -->
            </span>
          </td>
          <td class="cmp__cell">
            <span class="cmp__rating" aria-label="3 out of 5">
              <!-- 3 filled, 2 empty pips -->
            </span>
          </td>
          <td class="cmp__cell">
            <span class="cmp__rating" aria-label="2 out of 5"><!-- 2 filled pips --></span>
          </td>
        </tr>

        <!-- Category toggle row (used when showCategoryToggles is true) -->
        <tr class="cmp__category-row">
          <td colspan="4" class="cmp__category-cell">
            <button type="button" class="cmp__category-toggle" aria-expanded="true">
              <svg class="cmp__chevron cmp__chevron--expanded" viewBox="0 0 16 16"
                   width="14" height="14" fill="none" aria-hidden="true">
                <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5"
                      stroke-linecap="round" stroke-linejoin="round"/>
              </svg>
              Pricing
            </button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>

  <div class="cmp__footer">
    <a class="pbc pbc--solid pbc--rounded" href="/signup">Start free trial</a>
  </div>
</section>

<script>
/* Toggle category visibility when the button is clicked. */
(function () {
  document.querySelectorAll('.cmp__category-toggle').forEach(function (btn) {
    btn.addEventListener('click', function () {
      var expanded = btn.getAttribute('aria-expanded') === 'true';
      btn.setAttribute('aria-expanded', String(!expanded));
      btn.querySelector('.cmp__chevron').classList.toggle('cmp__chevron--expanded', !expanded);
      /* Show/hide the sibling rows until the next category row. */
      var row = btn.closest('tr').nextElementSibling;
      while (row && !row.classList.contains('cmp__category-row')) {
        row.hidden = expanded;
        row = row.nextElementSibling;
      }
    });
  });
}());
</script>

Customization

Override these CSS custom properties to integrate the component into your brand.

.cmp {
  --cmp-bg: var(--brand-surface, #ffffff);
  --cmp-cell-bg: var(--brand-surface, #ffffff);
  --cmp-cell-bg-highlighted: rgba(30, 95, 207, 0.05);
  --cmp-border: rgba(0, 0, 0, 0.1);
  --cmp-text-color: var(--brand-ink, #102542);
  --cmp-muted-color: rgba(16, 37, 66, 0.55);
  --cmp-icon-check: var(--brand-accent, #1e5fcf);
  --cmp-icon-x: rgba(16, 37, 66, 0.3);
  --cmp-rating-filled: var(--brand-accent, #1e5fcf);
  --cmp-rating-empty: rgba(16, 37, 66, 0.15);
  --cmp-category-bg: rgba(0, 0, 0, 0.025);
  --cmp-radius: 1rem;
  --cmp-cell-padding: 0.875rem 1rem;
  --cmp-section-padding: 2rem;
}