NewMCP Apps — interactive section cards now render inside your AI chatRead the changelog
Craft

Accessible pricing tables: what screen readers actually announce

A pricing table is a table. Most are built as cards, and screen readers read them as a list of unrelated numbers. Fixing it takes less markup than you think.

IM2 min readUpdated
On this page 3 sections

In short

Build the comparison as a real table with column headers for plans and row headers for features, or, if you use cards, give each card a heading with the plan name and put the price in the same text run. Screen readers then announce each price with the plan it belongs to.

Key takeaways

  • A feature comparison is a table — mark it up as one.
  • Every price needs its plan name nearby in the reading order.
  • Do not put meaning in colour or a tick icon alone.
  • Test with a screen reader for five minutes before shipping.

Pricing pages are where accessibility shortcuts cost money. If a screen-reader user cannot tell which price belongs to which plan, they leave. Here is what the most common patterns sound like, and how to fix each.

Cards: the most common pattern#

Three cards side by side look clear. Read aloud, a typical implementation announces “29, per month, Start free, 79, per month, Start free” — numbers without context. The fix is a heading in each card that names the plan, placed before the price.

PatternWhat a screen reader saysFix
Cards without headings“29, per month, button Start free”Add an h3 with the plan name first
Ticks as icons only“image, image, image”Add visually hidden text: “Included”
Toggle without a state“button Annual”Use aria-pressed or a radio group

The comparison table#

Below the cards, most pages compare features. That is tabular data, and a table with th scope="col" for plans and th scope="row" for features lets a screen reader announce “Pro, Custom domains, Included” for every cell.

html
<table>
  <thead><tr><th></th><th scope="col">Free</th><th scope="col">Pro</th></tr></thead>
  <tbody>
    <tr><th scope="row">Custom domains</th><td>—</td><td>Included</td></tr>
  </tbody>
</table>

The monthly/annual toggle#

A toggle that changes every price on the page needs to say which state it is in. Two radio buttons labelled Monthly and Annual are the simplest accessible version, and they are what our pricing sections ship with.

None of these fixes change how the page looks. They change what it says to the people who cannot see it — which, on a pricing page, is the difference between a customer and a bounce.

How we know this

We tested twelve popular SaaS pricing pages with VoiceOver and NVDA in August 2026.

Related questions

Should a pricing comparison be a table?

Yes. Plans against features is tabular data; a real table lets screen readers announce each value with its row and column.

Are tick icons accessible?

Only with text. Add visually hidden “Included” or “Not included”.

How do I make the billing toggle accessible?

Use a radio group, or a button with aria-pressed, so its state is announced.

Comments

No comments yet — be the first to share a thought.

Leave a comment

Your email is never shown. Comments are reviewed before they appear.

Links in comments are marked ugc nofollow.

Keep reading

All articles