HTML Formatterhtmlformatteronline.com

Feature comparison table

A plan comparison with a sticky header row and a highlighted column.

tablecomparisonpricingstickyfeatures
<div class="wrap">
  <table class="compare">
    <caption>What is included in each plan</caption>
    <colgroup>
      <col><col><col class="best"><col>
    </colgroup>
    <thead>
      <tr>
        <td></td>
        <th scope="col">Starter<span>Free</span></th>
        <th scope="col">Studio<span>£24/mo</span><b class="flag">Recommended</b></th>
        <th scope="col">Agency<span>£79/mo</span></th>
      </tr>
    </thead>
    <tbody>
      <tr><th scope="row">Projects</th><td>1</td><td>10</td><td>Unlimited</td></tr>
      <tr><th scope="row">Requests a month</th><td>10,000</td><td>500,000</td><td>5,000,000</td></tr>
      <tr><th scope="row">Custom domains</th>
        <td><span class="no" role="img" aria-label="Not included">✕</span></td>
        <td><span class="yes" role="img" aria-label="Included">✓</span></td>
        <td><span class="yes" role="img" aria-label="Included">✓</span></td></tr>
      <tr><th scope="row">Team members</th><td>1</td><td>5</td><td>Unlimited</td></tr>
      <tr><th scope="row">Support</th><td>Community</td><td>Email, same day</td><td>Priority</td></tr>
      <tr><th scope="row">Single sign-on</th>
        <td><span class="no" role="img" aria-label="Not included">✕</span></td>
        <td><span class="no" role="img" aria-label="Not included">✕</span></td>
        <td><span class="yes" role="img" aria-label="Included">✓</span></td></tr>
      <tr><th scope="row">Invoiced billing</th>
        <td><span class="no" role="img" aria-label="Not included">✕</span></td>
        <td><span class="no" role="img" aria-label="Not included">✕</span></td>
        <td><span class="yes" role="img" aria-label="Included">✓</span></td></tr>
    </tbody>
    <tfoot>
      <tr>
        <td></td>
        <td><a class="btn ghost" href="#">Start free</a></td>
        <td><a class="btn" href="#">Choose Studio</a></td>
        <td><a class="btn ghost" href="#">Choose Agency</a></td>
      </tr>
    </tfoot>
  </table>
</div>

How it works

colgroup does the column highlight. One <col class="best"> gives the whole column a background, with no class on any individual cell. It is one of the few things <col> can style — background, border, width and visibility — and this is exactly the case it exists for.

border-collapse: separate is required for sticky headers. With collapse, borders belong to the table rather than the cells, and sticky cells lose them when they detach. Using separate with border-spacing: 0 looks identical and keeps the borders attached.

The top-left cell is a td, not a th. It is empty and labels nothing. Making it a header tells a screen reader it describes either the row or the column, which it does not.

Ticks and crosses need labels. A bare ✓ is announced inconsistently — sometimes as "check mark", sometimes as nothing at all. role="img" with an aria-label makes it announce "Included" reliably, which is the actual information.

min-width plus an overflow wrapper. Below 620 pixels the table scrolls sideways rather than compressing to unreadable columns. For a comparison table this is the right call — the whole point is seeing columns side by side, so stacking them defeats it.

Accessibility notes

scope="col" on the plan names and scope="row" on the feature names is what lets a screen reader announce "Custom domains, Studio, Included" when the user lands on a cell. Without scope, they hear "Included" with no context at all.

The scrolling wrapper should take tabindex="0" and an aria-label in production, so keyboard users can scroll it without a mouse. It is omitted here to keep the markup readable.

Never signal availability with colour alone. The tick and the cross differ in shape as well as colour, and both carry a text label underneath.

Making it yours

For more than four plans, a comparison table stops working and a filtered feature list serves better. Three or four is the range where side-by-side comparison actually helps someone decide.

Group long feature lists with <tbody> sections and a spanning header row per group: <tr><th colspan="4" scope="colgroup">Security</th></tr>.

The pricing table template is the card-based alternative, which works better on mobile and worse for detailed comparison. Many sites use both: cards at the top, this table further down.

Related templates

Check your version

Once you have edited this, the HTML validator will catch any tag you left unclosed, and the formatter will tidy the indentation. Both run in your browser.