Add Table Headers and Scope Attributes
Learn how to make HTML tables accessible with proper headers and scope attributes. Includes code examples.
What Is It?
Table accessibility requires: <th> tags for headers, scope attribute ("row" or "col") on headers, proper nesting of <thead>, <tbody>, <tfoot>. Screen readers use this structure to announce cell relationships.
Affected users: Screen reader users, cognitively disabled users, users with dyslexia, users on complex data entry
Why It Matters
Without headers and scope, screen reader users hear isolated cell values with no context (e.g., '600' with no indication it's a revenue figure for Q4). Headers make tables understandable.
How to Detect This Issue
Test with screen reader by navigating table cells. Check if headers are announced with cell content. Inspect HTML for <th> tags, scope attributes, and proper table structure.
Code Examples & Fixes
HTML / CSS
<table>
<tr><td>Month</td><td>Revenue</td></tr>
<tr><td>Jan</td><td>$10,000</td></tr>
<tr><td>Feb</td><td>$12,000</td></tr>
</table><table>
<thead>
<tr><th scope="col">Month</th><th scope="col">Revenue</th></tr>
</thead>
<tbody>
<tr><th scope="row">January</th><td>$10,000</td></tr>
<tr><th scope="row">February</th><td>$12,000</td></tr>
</tbody>
</table>Use <th> for headers, scope="col" for column headers, scope="row" for row headers. Screen readers announce: 'January, Revenue, $10,000'.
React / Next.js
const Table = () => <table><tr><td>{data}</td></tr></table>const Table = () => <table>
<thead><tr><th scope="col">Name</th><th scope="col">Role</th></tr></thead>
<tbody>{data.map(person => <tr key={person.id}><th scope="row">{person.name}</th><td>{person.role}</td></tr>)}</tbody>
</table>For dynamic tables, map over data but ensure <th> and scope are properly generated. Use scope="row" for row headers, scope="col" for column headers.
WordPress
Table inserted in WordPress editor, all cells as <td>First row set as header (WordPress editor: highlight row, right-click, "Modify table", mark header). Scope is automatic in WordPress.WordPress table editor allows marking rows/cells as headers. Use "Modify table" to set header cells properly.
Shopify Liquid
Pricing table built with <div> grid layout, not semantic <table>Pricing table built with proper <table>, <th scope="col"> for pricing column headers, <th scope="row"> for plan namesFor data tables, use semantic <table>. If using CSS grid for layout, don't force table semantics. But for data, <table> is correct.
Common Mistakes
Using <td> for headers instead of <th>
Omitting scope attribute from <th> tags
Not nesting <thead>, <tbody>, <tfoot> properly
Complex tables (multi-level headers) without proper structure
Using <table> for layout instead of actual data (don't do this)
Frequently Asked Questions
What's the difference between scope="col" and scope="row"?
What about complex tables with merged cells?
Should I use <caption> in tables?
Can I use table for layout?
Check your website for free
Get your ADA, WCAG, privacy & security score in 90 seconds.