Search topics
Data tables with headers that are only bold text
The header row is styled to look like headers, but every cell is a td, so nothing ties a value to its row or column.
The problem
Screen reader users hear numbers without knowing which row or column they belong to.
A pricing page compares plans in a table. The top row reads Plan, Monthly and Yearly in bold, and each row below starts with a plan name. A sighted reader glances up and left to see what a figure means. A screen reader user moving through the cells hears "$12", "$120", "$30", "$300", and nothing else. To learn that "$30" is the Team plan's monthly price, they have to move back to the top of the column and to the start of the row, and keep both in mind.
Every cell is a td. The header row is bold because of a class, and the plan names are bold because of a style on the first column. Bold is a visual relationship; it is not in the markup. WCAG 1.3.1 Info and Relationships requires that structure shown visually is also available in code, and in a data table the main structure is which header applies to which cell. With no th and no ARIA header roles, the browser has nothing to pass on, so screen readers cannot announce headers with each value.
The table is small here, and counting back is only tiring. In a timetable, a results table or a comparison with a dozen columns, the person has to rebuild the grid from memory, and a single slip gives them the wrong price or the wrong time.
1 of 10 groups affected
- No vision (affected)
- Low vision (not affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (not affected)
- Reach, strength (not affected)
- Cognitive (not affected)
- Seizures (not affected)
- Without vision A screen reader moving through the table reads each value alone, so "30" could be any plan and any billing period until the person goes back to the top row and counts across.
A scanner can say that a table has no th, but not which cells ought to be headers, and some tools stay quiet when a table has a th that heads the wrong thing. It cannot tell whether scope points the right way, or whether a header row that sorting rebuilt still matches the data. AI code generators often produce a grid of div elements styled as a table, or add role="table" without the row and cell roles under it. Only reading the table with a screen reader, cell by cell, confirms that each value is announced with the headers a sighted reader would use.
Try it
Press Tab to move through the prices.
Each Tab stands in for a screen reader moving to the next cell. The panel shows what it announces. Can you tell what each price is for?
Broken Can you fix this?
| Plan | Monthly | Yearly |
| Starter | $12 | $120 |
| Team | $30 | $300 |
Screen reader says: (Tab to a price)
A recreated demo. The broken version is intentionally inaccessible.
The fix
Mark header cells as headers, and say which way each one reads.
- 1Make every header cell a
th, or acolumnheaderorrowheaderin a complete ARIA table. - 2Give each
thascope, or eachtditsheaders, never both in one table. - 3Keep layout out of tables, and data tables out of
role="presentation".
<table class="prices"> <tr class="head"> <td>Plan</td> <td>Monthly</td> <td>Yearly</td> </tr> <tr> <td class="plan">Starter</td> <td>$12</td> <td>$120</td> </tr> <tr> <td class="plan">Team</td> <td>$30</td> <td>$300</td> </tr></table>Why this fix: Use native HTML first
The head and plan classes only make the text bold. Every cell is data, so a screen reader announces "$30" alone.
Each header is a th, and scope says whether it heads its column or its row. A screen reader now announces "Team, Monthly, $30" on that cell, and when the person moves down a column it repeats only the row header that changed. th is bold by default, so the old classes can go.
All three pass 1.3.1 on this table. Use th with scope: it does the same job as the other two with the least code, needs no ids to keep in step, and works in every screen reader and browser reading mode. Reach for headers and id only when the table is irregular enough that scope cannot say which headers apply, and for the ARIA roles only when the markup cannot be a table. In that case, check every role is in place, since a missing one silently breaks the grid.
Several things change how a header row behaves after the markup is right.
thwithoutscope: browsers infer the direction in a simple table with one header row, but addingscopecosts nothing and holds when someone later adds a row header.- Responsive tables: CSS that sets
display: blockorgridon table elements can remove their table semantics in some browsers. Test the narrow layout with a screen reader, or keep the table and let it scroll inside a named, focusable region. - Sorting and filtering: a sort that rebuilds the body must keep the row
th. Say which column is sorted witharia-sort, which is a separate topic. - Layout tables: a table used only to place content should not have
thorcaption. Give itrole="presentation", or better, replace it with CSS layout.
A caption names the table so that people moving between tables can tell them apart. It is covered by its own guide, but it belongs in the same component.
Fixing this with an AI coding assistant? Get this guide as Markdown
Verify the fix
5 checks, no mouse.
Test with: Screen reader, Keyboard, Zoom
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the table component changes.
Limits
This is a starting pattern, not a guarantee for every table. Screen readers differ in how much header text they repeat and when. Tables with headers several levels deep can be hard to follow even with perfect markup; where the data allows, split them into simpler tables.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
Keep this fixed as your code changes.
Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.