Matterhorn 15-003 · PDF/UA-1 UA1:7.5-2

TH cells need a Scope attribute — or Headers and IDs

Blocker Checkpoint 15: Tables WCAG 1.3.1 (A)

Header cells must declare whether they head a row, a column, or both, unless the table instead uses explicit /Headers ID references on every data cell.

The condition, as the protocol states it. “In a table not organized with Headers attributes and IDs, a <TH> cell does not contain a Scope attribute.”

Matterhorn Protocol 1.1, checkpoint 15, index 15-003, section UA1:7.5-2. Software can decide this one on its own.

What a screen reader actually does

The reader guesses the association, and guesses wrong on any table more complex than a simple grid.

What is actually in the file

A table cell is a structure element like any other. What makes it a header is its type, and what makes the header useful is an attribute:

24 0 obj
<< /Type /StructElem
   /S /TH
   /P 23 0 R
   /Pg 3 0 R
   /A << /O /Table /Scope /Column >>
   /K [ 11 ]
>>
endobj

The /A entry is the attribute dictionary. /O /Table declares the attribute owner — the namespace the attribute belongs to — and /Scope says what this header governs. Without it the element is still a TH, but a processor has no way to know whether it labels the cells below it or the cells beside it.

PDF/UA-1 clause 7.5 gives you two mutually exclusive ways to express header associations:

  • /Scope on each TH. Simple, and sufficient for a regular grid.
  • /Headers on each data cell, holding an array of /ID strings that name the header cells governing it. Verbose, and the only option once the table is irregular.

Condition 15-003 fires when a table uses neither: a TH with no /Scope, in a table where no cell carries /Headers.

What goes wrong without it

Screen readers have to guess, and their guesses differ. JAWS, NVDA and VoiceOver each apply their own fallback — usually “first row is column headers, first column is row headers” — and each gets a different answer on anything that is not a plain grid.

On a simple table you often get away with it. On a table with a stub column, a totals row, or a second header row, the reader announces confidently wrong headers, which is worse than announcing none: the user has no signal that anything is off.

How to fix it free, in Acrobat Pro

Acrobat can do this, through a dialog most people never find.

  1. Open the Tags panel and find the <Table> element.
  2. Right-click the table → Table Editor. (If it is greyed out, the table structure itself is broken — fix that first; that is condition 09-004.)
  3. The page now shows a coloured grid over the table. Red cells are TH, grey are TD.
  4. Any header cell showing grey: right-click it → Table Cell Properties → set Type to Header Cell.
  5. In the same dialog, set Scope to Row, Column or Both.
  6. Repeat for every header cell. You can shift-click a run of cells in one row and set the scope for all of them at once — this is the one place Acrobat gives you multi-select, and it saves real time.
  7. Close the Table Editor and re-run Accessibility → Accessibility Check.

The faster free route, if you still have the source: in Word, select the header row and tick Table Tools → Layout → Repeat Header Rows, and in the table properties tick Repeat as header row at the top of each page. Word then exports the row as TH with /Scope /Column already set. In InDesign, convert the row to a header row in Table → Convert Rows → To Header. Doing it upstream takes seconds and survives the next revision.

Where Scope stops being enough

Consider a table whose first header row spans years and whose second row splits each year into quarters:

2025202520262026
RegionQ1Q2Q1Q2
North1234

The cell containing 3 is headed by North, by Q1, and by 2026. /Scope cannot say that: it has no way to express “the 2026 header governs columns 4 and 5 only”. Every data cell needs /Headers:

<< /Type /StructElem /S /TD
   /A << /O /Table /Headers [ (north) (q1b) (y2026) ] >>
>>

and each header cell needs a matching /ID. Acrobat’s Table Editor exposes this through Table Cell Properties → Associated Header Cell IDs, one cell at a time. On a 5×20 table that is 100 dialogs.

Where Taggart differs

Taggart reports the two cases separately, because they need different work.

For 15-003 it names every TH missing a /Scope, and the fix is mechanical: Taggart infers Row or Column from the cell’s position — a cell in an all-TH row heads its column, a leading cell in a body row heads its row, a corner cell in a stacked header gets Both — and writes the attribute with the correct /O /Table owner. It ships in “Fix All Safe”.

For 15-005 it detects the tables where /Scope is genuinely insufficient — spanning cells or stacked header rows — and says so, rather than writing a /Scope that would look conformant and still mislead a reader.

How Taggart handles it

Taggart detects this condition automatically. It is reported as a blocker, with the page, the structure path and the object id of every occurrence.

The fix is Set table headers, Scope and Headers references. It changes what the document means, so Taggart proposes it and waits for your approval rather than applying it unattended.

Whatever Taggart changes, the page still looks identical. Fixes edit the PDF object model, not the content streams, and every remediation is re-rendered and compared against the original at structural similarity ≥ 0.999 before you get the file back. A tool that quietly reflows your document is worse than no tool.

Frequently asked questions

Do I need Scope on every TH, or only the first row?

Every TH, unless the table uses Headers and IDs instead. The protocol's condition is per-cell: any TH in a table not organised with Headers references and lacking a Scope attribute is a failure. In practice that means the whole header row and, in most data tables, the first cell of every body row too.

What are the valid values?

Row, Column and Both. Nothing else. /Scope /None, /Scope /Disc and similar values are failures — they usually mean the producer wrote a placeholder. Both is for a corner cell that heads its row and its column at once.

When should I use Headers and IDs instead of Scope?

As soon as the table has merged cells or more than one header row. Scope says 'this cell heads its row' or 'its column' and nothing more, which cannot express a header that governs three of five columns. At that point every data cell needs an explicit /Headers array of /ID references. That is condition 15-005.

Does this apply to layout tables?

It should not, because content laid out in a grid for appearance should not be tagged as a table at all — that is condition 15-004. If you have a layout table, the fix is to retag it as ordinary paragraphs and containers, not to add Scope to it.

Where this sits in the standards

Standards this condition maps to
StandardReference
Matterhorn Protocol 1.1 Checkpoint 15 (Tables), index 15-003
PDF/UA-1 (ISO 14289-1) Clause 7.5-2
WCAG 2.1 1.3.1 Info and Relationships — Level A
EN 301 549 / Section 508 / ADA Title II All three point at WCAG 2.1 Level AA for non-web documents, so a Level A or AA criterion here is in scope for each of them.

Other conditions in checkpoint 15

Checkpoint 15 covers tables. These are its other failure conditions; the ones without a link are in the rule set but do not have a written page yet.

Check your own file. Taggart’s validator is free and unlimited — every machine-checkable Matterhorn condition, no page cap, no watermark, no account needed.

Check a PDF free

Verified against a real document on . Last updated .