The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Create a data table | <table>
<tr>
<th>Plan</th>
<th>Price</th>
</tr>
<tr>
<td>Basic</td>
<td>$9</td>
</tr>
</table> | View examples |
| Name the table | <table>
<caption>Monthly subscriptions</caption>
<tr>
<th>Plan</th>
<th>Users</th>
</tr>
</table> | View examples |
| Group header rows | <thead>
<tr>
<th scope="col">Product</th>
<th scope="col">Sales</th>
</tr>
</thead> | View examples |
| Group body rows | <tbody>
<tr>
<th scope="row">Keyboard</th>
<td>42</td>
</tr>
</tbody> | View examples |
| Group summary rows | <tfoot>
<tr>
<th scope="row">Total</th>
<td>42</td>
</tr>
</tfoot> | View examples |
| Mark a column header | <th scope="col">Revenue</th> | View examples |
| Mark a row header | <th scope="row">January</th> | View examples |
| Expand a short header | <th scope="col" abbr="Quantity">Qty</th> | View examples |
| Span header columns | <th scope="colgroup" colspan="2">First half</th> | View examples |
| Span header rows | <th scope="rowgroup" rowspan="2">Europe</th> | View examples |
| Group table columns | <colgroup>
<col class="label-column">
<col span="2" class="number-column">
</colgroup> | View examples |
| Identify a complex header | <th id="q1-sales">Q1 sales</th> | View examples |
| Associate several headers | <td headers="europe q1-sales">120</td> | View examples |
| Expose sort direction | <th scope="col" aria-sort="ascending">
<button type="button">Name</button>
</th> | View examples |
Use tables for genuinely tabular relationships, not page layout. Start with a caption, mark every header with th, organize long tables into row groups, and keep header relationships simple enough that scope can describe them before reaching for explicit IDs and headers attributes.
Step by step
Detailed examples
Start with rows, cells, and a caption
A table represents data whose meaning depends on row and column relationships. Put a caption directly inside table so users can identify that data set, use th for labels, and use td for values. Avoid a table when a list, definition list, or ordinary layout communicates the content more clearly.
<table>
<caption>Monthly subscriptions</caption>
<tr>
<th scope="col">Plan</th>
<th scope="col">Price</th>
</tr>
<tr>
<td>Basic</td>
<td>$9</td>
</tr>
</table> Organize header, body, and footer rows
thead, tbody, and tfoot communicate structural row groups and give CSS stable styling hooks. They do not replace individual header cells: cells that label data still need th. A table may have multiple tbody groups, but its rows must form a valid rectangular grid.
<table>
<caption>Units sold</caption>
<thead><tr><th scope="col">Product</th><th scope="col">Sales</th></tr></thead>
<tbody><tr><th scope="row">Keyboard</th><td>42</td></tr></tbody>
<tfoot><tr><th scope="row">Total</th><td>42</td></tr></tfoot>
</table> Describe straightforward header relationships
For a simple table, scope col and scope row make the intended relationship explicit and resilient. The abbr attribute can provide a shorter or expanded alternative for a header, but the visible header should remain understandable on its own.
<table>
<caption>Inventory by month</caption>
<thead><tr><th scope="col">Month</th><th scope="col" abbr="Quantity">Qty</th><th scope="col">Revenue</th></tr></thead>
<tbody><tr><th scope="row">January</th><td>18</td><td>$540</td></tr></tbody>
</table> Use spans only for real header groups
colspan and rowspan change the table grid and are useful for grouped headings. Match their numeric spans to the actual cells covered and use colgroup or rowgroup scope on group headers. Excessive spanning makes both responsive presentation and header associations harder to understand.
<table>
<caption>Regional sales</caption>
<thead>
<tr><th rowspan="2" scope="col">Region</th><th colspan="2" scope="colgroup">First half</th></tr>
<tr><th scope="col">Q1</th><th scope="col">Q2</th></tr>
</thead>
<tbody>
<tr><th rowspan="2" scope="rowgroup">Europe</th><td>120</td><td>135</td></tr>
<tr><td>118</td><td>142</td></tr>
</tbody>
</table> Note: Test complex spanning structures with the screen readers and browsers your audience uses.
Attach styling hooks to columns
colgroup and col describe columns without adding repeated classes to every cell. They support a limited set of presentational effects through CSS, and they do not create semantic headers or accessible names by themselves.
<table>
<caption>Project budget</caption>
<colgroup>
<col class="label-column">
<col span="2" class="number-column">
</colgroup>
<tr><th scope="col">Team</th><th scope="col">Budget</th><th scope="col">Actual</th></tr>
<tr><th scope="row">Design</th><td>$20k</td><td>$18k</td></tr>
</table> Associate complex cells explicitly
When scope cannot express a genuinely complex, irregular relationship, give each relevant th a unique id and list those IDs in the data cell's headers attribute. This is verbose and easy to break during edits, so simplify the table or split it into smaller tables when practical.
<table>
<caption>Quarterly sales</caption>
<tr><th></th><th id="q1-sales">Q1 sales</th></tr>
<tr><th id="europe">Europe</th><td headers="europe q1-sales">120</td></tr>
</table> Expose sortable table state
Put aria-sort only on the header for the column currently controlling sort order, and update it after each sort. A real button provides keyboard interaction and an accessible action name; aria-sort alone does not implement sorting behavior.
<table>
<caption>Team members</caption>
<thead><tr><th scope="col" aria-sort="ascending"><button type="button">Name</button></th><th scope="col">Role</th></tr></thead>
<tbody><tr><td>Ada</td><td>Engineer</td></tr></tbody>
</table> Note: JavaScript must update both row order and aria-sort when the user activates a sort button.
Local code tester
Try an accessible data table
Edit the caption, headers, row groups, and values, then render the table locally.
Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



