F90: When Table Headers Point to the Wrong Place
Patricia · AI Research Engine
Analytical lens: Risk/Legal Priority
Government compliance, Title II, case law
AI-assisted · Source-linked · Editorially reviewed · Methodology
Trust note
This article was drafted with AI assistance, reviewed against accessibility.chat editorial standards, and should be treated as research and education rather than legal advice. We prioritize primary sources and correct material errors.

37%. That's the maximum share of accessibility failures that automated testing tools detect, according to research comparing automated and manual audit methodologies. Broken headers and id associations in complex HTML tables sit squarely in that undetected majority — syntactically valid, semantically broken, and invisible to most scanners.
WCAG Failure F90 (opens in new window) documents exactly this pattern: a specific, reproducible way that developers incorrectly associate table headers and data cells using the id and headers attributes. The failure isn't theoretical. It's the kind of error that happens when someone copies a table structure, updates the visible content, and forgets to update the attribute wiring underneath. The result is a table that looks correct to every sighted user and passes most automated checks — while delivering completely wrong header context to screen reader users.
The Failure
The headers attribute exists for complex tables where a simple <th scope> relationship isn't sufficient — tables with nested or multi-level headers where a single data cell belongs to more than one header simultaneously. The mechanism requires that each <th> carries a unique id, and each <td> lists the relevant header IDs in its headers attribute.
F90 describes what happens when that association goes wrong. The official W3C example (opens in new window) uses a grade table with two header levels: top-level headers for "Exams" and "Projects," and second-level headers for "1," "2," and "Final" under each. Here's the broken pattern:
<table> <tr> <th rowspan="2" id="h">Homework</th> <th colspan="3" id="e">Exams</th> <th colspan="3" id="p">Projects</th> </tr> <tr> <th id="e1" headers="e">1</th> <th id="e2" headers="e">2</th> <th id="ef" headers="e">Final</th> <th id="p1" headers="p">1</th> <th id="p2" headers="p">2</th> <th id="pf" headers="p">Final</th> </tr> <tr> <td headers="h">15%</td> <td headers="e p1">15%</td> !-- should be "e e1" --> <td headers="e p2">15%</td> !-- should be "e e2" --> <td headers="e pf">15%</td> !-- should be "e ef" --> <td headers="e p1">15%</td> !-- should be "p p1" --> <td headers="e p2">15%</td> !-- should be "p p2" --> <td headers="e pf">15%</td> !-- should be "p pf" --> </tr></table>Two errors compound here. First, every data cell in the Projects columns references e (Exams) as its top-level header instead of p (Projects). Second, the second-level column header references are cross-wired — cells under Exams reference Project sub-headers and vice versa. The table renders identically in a browser. The programmatic structure is wrong.
This failure violates Success Criterion 1.3.1: Info and Relationships (opens in new window), which requires that information conveyed through visual presentation — including the hierarchical relationship between headers and data — be available to assistive technologies.
Why This Matters
For a screen reader user navigating cell by cell through a data table, the announced header context is the only way to understand what a value means. When NVDA or JAWS reads a cell in a complex table, it announces the associated headers before or alongside the cell value. If those associations are wrong, the user hears "Exams, Final: 15%" when the actual meaning is "Projects, Final: 15%" — a materially different piece of information.
In a grade table, that's a confusing experience. In a financial disclosure, a medical reference table, or a government benefits schedule, it's a barrier to understanding information that may have real consequences. The visual layout communicates the correct structure; the programmatic layer does not. That gap is precisely what 1.3.1 (opens in new window) is designed to close.
Users who rely on keyboard navigation without a screen reader are less affected — they can see the visual structure. But users of braille displays, users with cognitive disabilities who rely on assistive technology to re-present table data, and users of voice control software that interacts with semantic structure all encounter the same broken layer.
The Fix
The corrected pattern ensures each data cell's headers attribute lists the IDs of every header that applies — the top-level category first, then the column sub-header:
<table> <tr> <th rowspan="2" id="h">Homework</th> <th colspan="3" id="e">Exams</th> <th colspan="3" id="p">Projects</th> </tr> <tr> <th id="e1" headers="e">1</th> <th id="e2" headers="e">2</th> <th id="ef" headers="e">Final</th> <th id="p1" headers="p">1</th> <th id="p2" headers="p">2</th> <th id="pf" headers="p">Final</th> </tr> <tr> <td headers="h">15%</td> <td headers="e e1">15%</td> !-- Exams → 1 --> <td headers="e e2">15%</td> !-- Exams → 2 --> <td headers="e ef">15%</td> !-- Exams → Final --> <td headers="p p1">15%</td> !-- Projects → 1 --> <td headers="p p2">15%</td> !-- Projects → 2 --> <td headers="p pf">15%</td> !-- Projects → Final --> </tr></table>Each data cell now references its correct top-level header and its correct second-level header. The programmatic structure matches the visual one. A screen reader navigating to the Projects Final cell will announce "Projects, Final: 15%" — which is what the table actually means.
For simpler tables where one header applies per column or row, the scope attribute on <th> elements is usually sufficient and less error-prone. Reserve the id/headers pattern for genuinely complex tables where scope cannot express the full relationship.
Applying This
The W3C explicitly notes that this failure often originates from copying table code and failing to update the attribute values. That's a workflow problem as much as a knowledge problem. A few targeted practices address it:
In code review: Treat any table with headers attributes as requiring explicit verification. Reviewers should trace each data cell's headers value back to the actual id values of the header cells that logically apply. This takes two minutes on a small table and is the only reliable way to catch the error.
In automated testing: Most automated tools will not flag this failure because the attributes are syntactically valid — they reference real IDs that exist in the DOM. Research on testing methodology confirms that semantic correctness of complex table associations requires human judgment. Flag any table using headers attributes for manual review as a standing policy.
In design handoff: If a design calls for a multi-level header table, annotate the header relationships explicitly in the design file before development begins. A clear mapping of which headers apply to which data regions prevents the copy-paste error at the source.
Quick diagnostic: Open browser DevTools, select any data cell, and inspect its headers attribute. Then verify each listed ID corresponds to a header cell that actually governs that data cell's column and row. If any ID belongs to a header in a different section of the table, F90 is present.
Data Table: F90 at a Glance
| Attribute | Failure Pattern | Correct Pattern | Detection Method |
|---|---|---|---|
headers on <td> | References wrong id values (e.g., Exams header on Projects data) | References all applicable header IDs in correct hierarchy | Manual review only |
id on <th> | Present and valid — no syntactic error | Present and valid | Automated tools (partial) |
| SC 1.3.1 compliance | Fails — programmatic structure mismatches visual structure | Passes — associations are accurate | Manual verification |
| Screen reader output | Announces incorrect header context | Announces correct header chain | User testing |
Language Access Intersection
Government data tables — budget summaries, program eligibility schedules, demographic reports — are exactly the content where F90 causes the most harm. These tables are also frequently translated for limited English proficient communities. A translated table that inherits broken headers associations from the source HTML compounds the problem: the content is now in another language, but the programmatic structure is still wrong.
idioma.chat (opens in new window) addresses this intersection directly. Unlike translation services that process only visible text, idioma.chat translates the full accessibility layer — including ARIA labels, alt text, and the structural attributes that screen readers depend on. When a table's header text is translated, the corresponding ARIA and structural relationships need to travel with it. A compliance team that treats language access and disability access as separate workstreams, as research on standards fragmentation documents, will miss exactly this kind of compounded failure. Title VI language access obligations and Title II disability access obligations converge on the same HTML — and both require that the programmatic structure be correct, not just the visible text.
F90 is a small failure document with a narrow scope. But the pattern it describes — technically valid code that silently misrepresents meaning to assistive technology — is one of the harder categories to govern at scale. The fix is straightforward once you know where to look. The challenge is building the review practices that ensure someone always does.
About the Patricia lens
A risk and legal lens. Frames findings around regulatory exposure, drawing on Title II obligations, published case law, and government compliance requirements.
Patricia is an AI analyst lens, not a human staff member. It helps frame this article through a consistent accessibility perspective.
Specialization: Government compliance, Title II, case law
View all articles using this lens →Primary source reviewed: https://www.w3.org/WAI/WCAG22/Techniques/failures/F90 (opens in new window)
Transparency Disclosure
This article was drafted with AI assistance and reviewed against our editorial methodology. We disclose that process so readers can judge the work clearly.