October 26, 2026·8 min read
How to Build an Accessible Pricing Table
Learn how to build an accessible pricing table with header cells, a mobile layout that keeps each price with its plan, and checkmarks paired with plain text.
An accessible pricing table lets every visitor answer the same question: what does each plan cost, and what does it include. The price, the plan name and each feature need to stay linked in the code, not only in the visual layout. That means real header cells, words next to checkmarks, and a small screen layout that does not strip the labels away.
A pricing table fails in a quiet way. It can look tidy on screen while a screen reader reads prices and checkmarks with no plan names attached.
Key takeaways
- Mark plan names as
thwithscope="col"and feature names asthwithscope="row". Bold text in atdis not a header. - Give the table a short
captionthat names what the table compares. - Put the word next to the icon. A checkmark alone tells a screen reader user little, and color alone tells a visitor who cannot tell colors apart even less.
- Keep the table a table on small screens, or rebuild it as cards with headings and lists.
- Make each plan button name the plan, for example "Choose Standard", instead of repeating the same button text for every plan.
Start with a real table and clear headers
A pricing table is a data table, so build it with table markup: table, caption, thead, tbody, th for headers and td for values. WCAG 2.2 success criterion 1.3.1, Info and Relationships, asks that information shown by layout also be available in the code or in text.
Headers run in two directions here. Plan names run across the top, and feature names run down the first column. Set each plan name to scope="col" and each feature name to scope="row". A screen reader can then announce the plan and the feature with each value, instead of reading a price on its own.
The corner cell needs a decision, not a blank. An empty header still gets announced, and it adds noise. Give it a short label such as "Feature".
Here is the header pattern:
<table>
<caption>Plan prices and features, per month</caption>
<thead>
<tr>
<th scope="col">Feature</th>
<th scope="col">Basic</th>
<th scope="col">Standard</th>
<th scope="col">Premium</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Price per month</th>
<td>$12</td>
<td>$24</td>
<td>$48</td>
</tr>
</tbody>
</table>
The prices in that fragment are a generic example only. They are not Axeazy prices. The point is the markup around them: plan names as column headers, the feature name as a row header, and a caption that states what the table holds.
Check your own table by viewing the page source and searching for th. If the plan row is made of td elements with bold text, change those cells to th and add scope.
Give checkmarks a text partner
Pricing tables lean on icons. A checkmark means included, a cross means not included, and a dash often means something in between that no one has defined. That shorthand works when you can see the icon and know the house style. It breaks when the icon is the only carrier of the meaning.
Pair every icon with a word. Write "Included" beside the checkmark and "Not included" beside the cross, in visible text. If space is tight, keep the word and drop a decorative flourish, not the other way round.
Hide the icon itself from assistive technology when the word already carries the meaning. Put aria-hidden="true" on the icon span, and let the word do the talking:
<td><span aria-hidden="true">✓</span> Included</td>
<td><span aria-hidden="true">✕</span> Not included</td>
Do not rely on color to separate included from not included either. WCAG 2.2 success criterion 1.4.1, Use of Color, says color must not be the only visual means of conveying information. A green checkmark and a grey cross can look alike to a visitor who cannot tell those colors apart, and they mean nothing to a screen reader. The word fixes both gaps at once.
Text contrast matters here too. WCAG 2.2 success criterion 1.4.3, Contrast (Minimum), sets a ratio of at least 4.5:1 for ordinary text, with 3:1 for large scale text. Check faint "Not included" text at the same standard as the price.
An accessible pricing table on a small screen
A pricing table that works on a desktop screen often breaks on a phone, because four or five plan columns do not fit side by side. Pick a layout that keeps the header links intact, and test it at a narrow width.
| Layout | What changes | What to watch |
|---|---|---|
| Scrolling table | Table scrolls sideways | Keyboard must reach the scroll area |
| Stacked cards | Each plan becomes a card | Feature names must repeat in each card |
| Fewer columns | Only compared plans stay in the table | Do not hide a plan visitors need to compare |
The scrolling table is the smallest change. It keeps th, scope and the caption, and every plan in one comparison. Its risk is discovery: a visitor may not notice the table scrolls. A visible scroll cue helps, and a keyboard check will show whether the scroll area can be reached without a mouse.
Stacked cards work when each card repeats the feature names as list labels, so "Included" still sits beside a named feature. Cards fail when the feature name appears once at the top of the page and each card holds only icons. That is the same failure as a headerless table, dressed up as a mobile pattern.
Whichever layout you pick, read one plan from top to bottom at a narrow width on a phone. Confirm each price still sits with its plan name, each feature word still sits with its value, and each button still names its plan. Then move through the page with the Tab key only, and confirm you can reach every button and every scroll area.
Put prices and buttons in plain words
State the billing period in the caption or in the price row, for example "per month", and state it the same way for every plan. A table that mixes "per month" in one column and a bare number in another invites a wrong comparison. If a plan has a setup fee, a limit, or a condition, put that condition in text near the price, not only in a footnote far below the table.
Use the header fragment above as the shape to aim for, with one price row, feature names as row headers, and words beside icons.
Buttons need the same plain treatment. A row of buttons that all read "Sign up" forces a screen reader user to work out which plan each button belongs to from its position. Name the plan in the button: "Choose Basic", "Choose Standard", "Choose Premium". Keep the button order the same as the column order.
If your pricing table sits inside a page builder, check the output markup, not only the editor preview. Builders often render plan names as styled div elements or as td cells, and icons without words.
How Axeazy helps with this exact issue
Axeazy lists table header structure among the 14 fix categories it works on, so a scan can flag tables where header markup is missing or wrong, and the fix work starts from the code rather than from a script laid over the page. For a site with a GitHub repository, fixes arrive as a pull request with a visible code diff for the owner or developer to review and merge. A scan is a starting point, and a quick manual keyboard and screen reader check is part of good practice, as described in the methodology notes.
Read the honest scope on the capabilities page, and for more depth see the scope attribute guide and the table header guide on our blog. Want a quick read on your own site? Run Axeazy's free scan . It takes about 60 seconds, needs no signup, and will show the table issues on your homepage.
Last checked: October 26, 2026.
FAQ
Do pricing tables need a caption?
A caption is a strong choice, because it gives the table a name that assistive technology announces when a visitor reaches it. Keep it short and factual, such as "Plan prices and features, per month".
Can I use a checkmark on its own if it looks clear?
No, not as the only carrier of the meaning. A checkmark alone gives a screen reader user an icon name or silence, and color alone gives a visitor who cannot tell colors apart no reliable cue. Put "Included" or "Not included" beside the icon in visible text.
Should I turn a pricing table into cards on mobile?
Cards can work, but only when each card repeats the feature names beside the values. A card that holds only icons loses the label for each value. If you keep the table, let it scroll sideways and check that the scroll area works with a keyboard.
What should a pricing table button say?
Name the plan in the button, for example "Choose Standard". A row of identical buttons forces a visitor to guess which plan each button belongs to from its position on the page.
Sources
- W3C WAI Tables Tutorial, checked October 26, 2026
- W3C Understanding Success Criterion 1.3.1, checked October 26, 2026
- W3C Understanding Success Criterion 1.4.1, checked October 26, 2026
- W3C Understanding Success Criterion 1.4.3, checked October 26, 2026
- MDN table element reference, checked October 26, 2026
Check your own site
See what is actually wrong with your site.
Free, 60 second WCAG 2.2 AA scan. No credit card required.
Scope of every report we ship: SCOPE LIMITATION: This report documents violations identified by automated WCAG 2.2 AA scanning using axe-core v4.11.0. Automated tools cannot test every WCAG criterion; many criteria require manual review. This report does not constitute legal advice, guarantee ADA compliance, or protect against legal action. ADA compliance is a legal determination made by courts. For full WCAG conformance assessment, engage an IAAP-certified accessibility professional.