In an HTML data table, what does the scope attribute on a <th> do, and what changes for a screen-reader user when it is missing?
answer
- headers must say what they head
- direction of the association matters
- col, row, colgroup, rowgroup
- missing scope falls back to guessing
- row labels belong in <th>, not <td>
basics
~20 sThe scope attribute declares which cells a <th> heads: scope="col" the column beneath it, scope="row" the row beside it. Without it, assistive technology has to guess the association from position, and in two-way tables it guesses wrong.
solid answer
~50 s`scope` says what a header cell is the header **for**. `scope="col"` means the `<th>` heads the column below it, `scope="row"` means it heads the rest of its row, and `scope="colgroup"` / `scope="rowgroup"` mean it heads a whole column or row group. Screen readers use that association to announce the relevant headers as the user moves cell by cell — so instead of hearing a bare "15.0" they hear "EMEA, Q4, 15.0", which is the only way to make sense of a cell out of context. If `scope` is absent, the browser and AT fall back on heuristics based on the cell's position, which usually works for a single header row but breaks down as soon as a table has both column headers and row labels. Marking row labels as `<td>` instead of `<th>` breaks it entirely — there is then no header to announce.
code
html · 22 lines<table>
<caption>Office hours</caption>
<thead>
<tr>
<td></td>
<th scope="col">Opens</th>
<th scope="col">Closes</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Monday</th>
<td>09:00</td>
<td>17:00</td>
</tr>
<tr>
<th scope="row">Saturday</th>
<td>10:00</td>
<td>14:00</td>
</tr>
</tbody>
</table>go deeper
Recall that header cells are <th> and data cells are <td>, and that scope="col" and scope="row" say which cells a header covers. Do not describe <th> as a styling choice.
Explain the association mechanism: <th> maps to columnheader or rowheader, scope fixes the direction, and screen readers announce the associated headers as the user crosses into a new row or column. Name all four scope values.
Demonstrate that you verify associations rather than assume them — reading the accessibility tree or navigating the table with a screen reader — and know the point at which scope stops being sufficient and explicit header association is required.
Own the standard: make correct header markup a property of the shared table component rather than of each page, decide how it is audited, and weigh whether a complex table should be redesigned into simpler ones instead of patched with more association markup.
## The problem scope solves A sighted reader resolves a table cell by glancing up to the column header and left to the row label. A screen-reader user cannot glance. They move through the grid one cell at a time, and the only thing that keeps them oriented is the assistive technology announcing the headers that apply to the current cell. That announcement requires the browser to know which header cells govern which data cells — the *header association*. `scope` is the simple, declarative way to state it. ## The values `scope` is an attribute of `<th>` (only `<th>`, never `<td>`) with four keywords: - `scope="col"` — this cell is the header for the rest of the column beneath it. - `scope="row"` — this cell is the header for the rest of the cells in its row. - `scope="colgroup"` — this cell heads all the columns in its column group. - `scope="rowgroup"` — this cell heads all the rows in its row group (a `<thead>`, `<tbody>`, or `<tfoot>`). The missing-value default is `auto`, which is exactly the guessing mode described below. ```html <table> <caption>Revenue by region</caption> <thead> <tr> <td></td> <th scope="col">Q1</th> <th scope="col">Q2</th> </tr> </thead> <tbody> <tr> <th scope="row">EMEA</th> <td>12.4</td> <td>13.1</td> </tr> <tr> <th scope="row">APAC</th> <td>8.9</td> <td>9.7</td> </tr> </tbody> </table> ``` Note the empty top-left corner cell: it heads nothing, so it is a `<td>`, not a `<th>`. ## What the semantics map to `<th>` is not "a bold, centred cell". It maps to a header role in the accessibility tree — `columnheader` or `rowheader` depending on its orientation — while `<td>` maps to an ordinary cell. Bold and centred is just the user-agent stylesheet; the role is the part that matters, and it is the part you throw away when you use `<td>` with a bold class for row labels. ## What actually happens without scope Browsers implement a fallback algorithm that walks up the column and left along the row looking for header cells. For a plain table with one header row and no row labels, this typically produces the right answer, which is why so many tables get away with omitting `scope`. Two things go wrong as tables get real: 1. **Two-way tables.** When a table has both column headers and a row-label column, the guess about which header applies in which direction becomes ambiguous, and different assistive technologies resolve it differently. The user hears the wrong header, or none. 2. **Spanning and multi-row headers.** Once `colspan`/`rowspan` are in play, or there are two stacked rows of headers, position alone no longer determines the relationship. That is the point where `scope` is not enough either, and explicit `headers`/`id` association takes over. The practical rule: write `scope` on every `<th>` in a data table. It costs nothing, it removes the dependency on heuristics, and it documents intent for the next developer. ## Related header details A `<th>` may also carry an `abbr` attribute giving a shortened form of the header text; some assistive technologies use it when repeating a long header for every cell in a column. It is optional and support varies — never put essential information only there. Empty header cells are a smell. If a `<th>` has no text, either it heads nothing (make it a `<td>`) or the label is missing and the column is unexplained for everyone. ## How to check it You cannot verify header association by looking at the page. Inspect the accessibility tree in devtools and confirm each header cell shows a `columnheader` or `rowheader` role, or navigate the table with a screen reader's table-navigation keys and listen to what is announced when you cross a row or column boundary. A table that reads as a stream of naked numbers is a table with broken associations, however tidy the markup looks.
- What is the difference between marking a row label as <th scope="row"> and just bolding the first <td>?Bolding is presentation only: the cell still maps to a generic cell role, so nothing is announced as that row's header and the row is unnavigable by header. `<th scope="row">` maps to a rowheader role and ties every data cell in the row to that label. The visual result can be identical; the accessibility tree is not.
- When do scope="colgroup" and scope="rowgroup" apply?When one header cell governs several columns or several rows rather than one. A `<th scope="colgroup">` spanning three columns above three per-column headers heads that whole group; `<th scope="rowgroup">` does the same for the rows of a `<tbody>` section. They express a level above the per-column or per-row header, not a replacement for it.
- If the browser guesses associations anyway, why write scope at all?Because the guess is a fallback, not a contract. It handles simple one-header-row tables and degrades on two-way tables, spanned headers, and stacked header rows, and different assistive technologies resolve ambiguity differently. Explicit `scope` makes the behaviour deterministic across AT and records the author's intent for the next reader.
saying these in an interview costs you the question
- Thinks <th> just means bold and centred text
- Puts scope on <td> cells
- Marks row labels as <td> with a CSS class
- Assumes browsers always infer the association correctly
- Believes scope is only needed when a table has spans