Skip to main content
All articles

October 26, 2026·8 min read

How to Use the Scope Attribute on Table Headers

Learn how the scope attribute tells screen readers which row or column a table header labels, with before and after code for col, row, colgroup and rowgroup.

accessibilitywcagtablestable-headersscreen-readerhtmlscope-attribute

The scope attribute tells assistive technology which cells a table header labels. Put it on a th element, set it to col, row, colgroup or rowgroup, and a screen reader can announce the right header when it reads a data cell. Without it, a table can look clear on screen and still read as a stream of numbers with no labels.

That is the core idea. A header cell is not only bold text at the top of a column. It is a label for a set of cells, and the code has to say which set. The scope attribute is the short, direct way to say it for most tables.

Key takeaways

  • Use th for header cells and td for data cells. Bold text in a td is not a header.
  • Use scope="col" for a header above a column and scope="row" for a header beside a row.
  • Use scope="colgroup" for a header over a group of columns and scope="rowgroup" for a header over a group of rows.
  • Define the group in the markup first. A column group needs colgroup, and a row group needs thead, tbody or tfoot.
  • When a table has two headers for one cell and the layout is not a simple grid, use id and headers instead.

What scope does in plain terms

A sighted reader sees a table as a grid. The position of a cell gives its meaning. A cell under "Price" and beside "Basic" is the price of the Basic plan. A screen reader does not get that position for free. It reads one cell at a time, and it needs the code to link each data cell back to its headers.

That link is what WCAG 2.2 success criterion 1.3.1, Info and Relationships, asks for. Information shown by layout has to be available in the code or in text as well. The W3C technique for this job is H63, which covers the use of the scope attribute to link header cells with data cells. Scope does not change how a table looks. It changes what assistive technology can announce.

The four scope values

Value Use it when Group markup needed
col The header labels one column None beyond the header row
row The header labels one row None beyond the header column
colgroup The header labels a group of columns A colgroup element
rowgroup The header labels a group of rows A thead, tbody or tfoot element

Most mistakes come from picking the wrong value. A header at the top of a column is col. A header at the left of a row is row, even if it sits in the second column.

A simple table with col and row

Start with a table that has headers in the top row and in the first column. This is the pattern for opening times, price lists and small comparison tables.

The wrong version uses td for everything and relies on bold text:

<table>
  <tr>
    <td><strong>Day</strong></td>
    <td><strong>Open</strong></td>
    <td><strong>Closed</strong></td>
  </tr>
  <tr>
    <td><strong>Monday</strong></td>
    <td>9 am</td>
    <td>5 pm</td>
  </tr>
</table>

A screen reader has no header to announce for "9 am" in that version. The fix is short:

<table>
  <tr>
    <th scope="col">Day</th>
    <th scope="col">Open</th>
    <th scope="col">Closed</th>
  </tr>
  <tr>
    <th scope="row">Monday</th>
    <td>9 am</td>
    <td>5 pm</td>
  </tr>
</table>

Now "Monday" labels its row and "Open" labels its column. When the reader lands on "9 am", it can announce both labels with the value. That is the whole change, and it is the change that makes the table usable without sight.

Try this check on your own page. View the source for one table and look for th and scope.

When colgroup and rowgroup are needed

Groups appear when a header spans more than one column or more than one row. A sales table might have a first level header called "2025" over two columns, "Produced" and "Sold". The first level header labels a group, not a single column.

First, define the group. For columns, use colgroup at the start of the table, with a col element for each column or a span count for the group. Then set the first level header to scope="colgroup" and the second level headers to scope="col":

<table>
  <caption>Units by year</caption>
  <colgroup><col><col></colgroup>
  <colgroup span="2"></colgroup>
  <tr>
    <th scope="col">Product</th>
    <th scope="colgroup" colspan="2">2025</th>
  </tr>
  <tr>
    <th scope="col">Item</th>
    <th scope="col">Produced</th>
    <th scope="col">Sold</th>
  </tr>
  <tr>
    <th scope="row">Widget A</th>
    <td>120</td>
    <td>98</td>
  </tr>
</table>

Read that code from the top. The colgroup elements define the groups before any row appears. The "2025" header then has a group to point to. Without the colgroup markup, scope="colgroup" has nothing to attach to, and the association is weak.

Row groups work the same way, with different elements. Wrap related rows in tbody, put header rows in thead and footer rows in tfoot, then use scope="rowgroup" on a header that spans the rows in that group. A table can have more than one tbody element, and each one defines a row group.

Add a short, factual caption when the table needs a title, for example "Units by year".

When scope is not enough

Scope covers a grid where each data cell has one row header and one column header, or one group header at each level. Some tables break that shape. A cell might need two column headers at different levels, or headers might sit in an offset column that is not the first column.

In that case, use id on each header cell and headers on each data cell, listing the header ids for that cell. That gives an explicit link for each cell, cell by cell. It takes more typing, and it is the right tool when the layout is too tangled for scope alone.

A simpler fix is often better than a clever one. If a table needs a long list of headers values to make sense, ask whether it should be two tables instead.

Common mistakes

  • Using td with bold text for headers. Change the cell to th and add scope.
  • Setting every header to scope="col", including the row headers in the first column. Row headers need scope="row".
  • Using scope="colgroup" without a colgroup element, or scope="rowgroup" without a thead, tbody or tfoot group.
  • Using a table for page layout. Layout tables force screen readers to announce structure that is not data, and they hide the real content order. Use CSS layout instead, and save tables for data.
  • Leaving the first row header blank or using an empty th as a spacer. An empty header still gets announced, and it adds noise. Restructure the table or use a caption to explain the layout.

How Axeazy helps with this exact issue

Axeazy lists table header structure among the 14 fix categories it works on. That means a scan can flag tables where header markup is missing or wrong, and the fix work starts from the code, not 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. For WordPress, the free plugin applies its three instant fixes at render time, and paid plans add weekly scans and other fixes. Read the honest scope on the capabilities page to see what is covered and what still needs a human check.

For a deeper read, see the table header guide on our blog.

Want a quick read on your own site? Run Axeazy's free 60 second scan . It needs no signup, and it will show the table issues on your homepage.

Last checked: October 26, 2026.

FAQ

Do I need scope if I already use th?

Yes, in most tables with headers in both directions. A plain th in the first row is often read as a column header, but a th in the first column is not always read as a row header without scope="row". Adding scope removes the guesswork and makes the link explicit for every header cell.

What is the difference between scope col and scope colgroup?

Scope col labels one column. Scope colgroup labels a group of columns that you have defined with a colgroup element. Use colgroup only when a header spans two or more columns, such as a year header over "Produced" and "Sold".

Can I use scope on a td element?

No. Scope belongs on th elements. If a cell is a header, mark it as th first, then add scope. Putting scope on a td does not turn that cell into a header.

When should I use headers and id instead of scope?

Use headers and id when a data cell needs more than one header at the same level, or when the header layout does not form a simple grid. Scope is the shorter path for most tables. Headers and id give a cell by cell link for the tables where scope cannot express the relationship.

Sources

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.