PDFPipe

Accessibility and structure / Table header tagging

Tagging table headers for screen readers

Marking which cells are headers and which data cells they govern, so a screen reader can announce "Quantity, 4" instead of reading a grid of bare numbers.

Where this API stands

This API does not produce this standard, and there is no option that would. What follows is what the requirement actually is and where a rendered document stands against it. This API writes no table structure elements, so there are no /TH cells, no /Scope and no /Headers in the output. This is a different concern from repeating a table header visually across pages, which is a CSS question and one you can solve today. What carries forward here is the markup: `<th scope="col">` is what a tagger promotes into a /TH with /Scope /Column, and `headers="qty-col total-row"` on a data cell in a complex table is what becomes the /Headers array. A table built from `<div>` elements with a grid layout offers a tagger nothing at all to promote, because there are no cells in it.

What the standard requires

The requirements as a checker enforces them, rather than as a procurement document paraphrases them. A file satisfies all of these or it satisfies none of them.

  • Header cells are /TH structure elements and data cells are /TD, and a table where everything is /TD has no headers as far as any tool is concerned.
  • Each /TH carries /Scope with /Column, /Row or /Both, which is what tells a reader which cells it applies to.
  • Complex tables, meaning ones with merged cells or two header rows, need explicit /Headers arrays on the data cells referencing header /ID values, because /Scope cannot express them.
  • /THead, /TBody and /TFoot group the rows, which matters for a table that continues across pages.
  • Column and row spans are declared with /ColSpan and /RowSpan on the cell, and a span that is present visually but absent from the structure produces a tree that does not match the table.

Who asks for this

Any accessibility audit of a document containing a table, which is to say almost every business document. It is also the checkpoint that auto-tagging most reliably gets wrong, so it is where the remediation budget goes.

What it looks like in a file

The concrete form of the thing being described, so you can recognise it in a document you have been handed rather than only in a specification.

html
<!-- Simple table: scope is enough. -->
<table>
  <thead>
    <tr>
      <th scope="col">Description</th>
      <th scope="col">Qty</th>
      <th scope="col">Line total</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Annual licence</th>
      <td>4</td>
      <td>1,240.00</td>
    </tr>
  </tbody>
</table>

<!-- Two header rows or merged cells: scope cannot express the
     relationship, so each data cell names its headers explicitly. -->
<table>
  <tr>
    <td></td>
    <th id="q1" scope="col">Q1</th>
    <th id="q2" scope="col">Q2</th>
  </tr>
  <tr>
    <th id="rev" scope="row">Revenue</th>
    <td headers="rev q1">1.2M</td>
    <td headers="rev q2">1.6M</td>
  </tr>
</table>

What to do about it

Write real tables. `<thead>` with `<th scope="col">` for column headings, `<th scope="row">` on the first cell of each row where rows are labelled, and `id` plus `headers` on anything with merged cells. Then tag the document downstream, and check the tables by hand, because they are the part auto-tagging gets wrong even when the markup is correct.

How to check a file rather than assume

PAC 2024 has a table inspector that renders the header associations cell by cell, and it will show you a data cell with no governing header as a gap. Acrobat's Table Editor does the same and lets you set scope by hand. Read one row aloud from what the tool reports and see whether it makes sense without the grid.

The mistake people make here

Using a header row that is styled rather than marked: a first row of `<td>` elements in bold with a background colour. It looks exactly like a header, every sighted reader treats it as one, and structurally it is a row of data cells, so a screen reader reads the numbers with no idea what column they are in.

Frequently asked

Does this API produce Table header tagging?

Not produced by this API. This API writes no table structure elements, so there are no /TH cells, no /Scope and no /Headers in the output. This is a different concern from repeating a table header visually across pages, which is a CSS question and one you can solve today. What carries forward here is the markup: `<th scope="col">` is what a tagger promotes into a /TH with /Scope /Column, and `headers="qty-col total-row"` on a data cell in a complex table is what becomes the /Headers array. A table built from `<div>` elements with a grid layout offers a tagger nothing at all to promote, because there are no cells in it.

Can I turn on the pdf_a option and be done?

No. The option writes an XMP packet declaring PDF/A-1b into the document catalogue, and the render response comes back carrying a warning that says in as many words that this is best-effort and that full conformance requires tagged structure and an embedded ICC profile. It is a claim written into the file, not a certificate earned by it, and a validator checks the claim against the bytes. Read the warning array in the response rather than taking the absence of an error as a pass.

Should I convert before rendering or after?

After, always. A conformance converter rewrites colour, embeds profiles, sets boxes and stamps metadata on a finished PDF. Trying to satisfy a conformance target from the source side means guessing at what the converter would have done, and the guesses that go wrong are expensive: flattening transparency you did not need to flatten changes what the page looks like.

Standards this one is confused with

The neighbours that matter are the ones people mistake for this, not the ones that sort next to it.

Conformance is decided after a file exists. Render the document, then check it, then convert only what the checker actually flags.