Rule handbook metadata
- Rule
DET.DATA.TABLE_HEADERS· lanedeterministic- Page status
- stale
- page_version
c7bf5ba070b6cf5956699b142b0ff07574630b940eec46477fb9ab7891e2f04e- generated_at
- 2026-05-19T22:35:00.000Z
- registry_fingerprint
6773fda516344e110b5a7b1435e655e1264e773825ca8bbe62194189891c42ba
How this rule is fixed
Pilot deterministic fixer: handbook_after · harness mode standalone · verify expect_rule_clean.
- Harness:
apply-harness-fixture-remediation.pycopies the After example HTML from this handbook page onto the defect fixture, theninvoke-det-ruleset-remediation-verify.shre-audits and expects zero findings for this rule. - Production sites:
run-website-ux-remediation-loop.shrunshandbook_html_patch(HTML patches inlib/ux-deterministic-fixers/fixers/patches/) before invoking the Cursor agent when the quality gate still fails.
Detection module: docs/design/ux-audit/deterministic-design-rules.md#det-data-table_headers. Scroll down for Before / After examples and Evidence and remediation steps.
Purpose
Kitchen Sink pages ship data tables beside charts and token grids—render_table in components/components.py, .forge-table-wrap on Srf (surfaces), Ctr (controls), Tkn (tokens), and consumer dashboards. Screen readers need a programmatic header relationship for each visible <td>: either <th scope="col|row|colgroup|rowgroup"> (or [role="columnheader"] / [role="rowheader"] with the same scope values), or headers on every body cell pointing at header ids inside the table.
This deterministic rule scans visible tables with at least one data cell, skips layout tables (role="presentation"), and flags tables with no header cells or <th> without valid scope when body cells lack headers wiring. Decorative or aria-hidden subtrees are ignored.
Plan: List tables in chart sections, IO matrices, and status grids. Do: Add a <thead> row of scoped <th> or wire headers on each <td>. Check: Re-run auditor metrics (tableHeadersReport) or inspect each .forge-table-wrap table in DevTools. Adjust: Fix generator output (render_table already emits scope='col'); repair hand-authored showcase HTML that omits scope.
Passing signals
render_tableoutput wraps tables in.forge-table-wrapwith<th scope='col'>for every column header (canonical KS helper).- Hand-authored tables follow
Ctr/controls.py:.forge-table-wrap>.table.table-smwith<th scope="col">on each header cell. - Row-header tables use
<th scope="row">for the first column when labels identify rows (for example methodology names in a comparison matrix). - Complex grids set
headers="col-a col-b"on each<td>referencing<th id="col-a">ids when scope alone is insufficient (merged cells, dual headers). role="columnheader"/role="rowheader"cells include the same validscopeattribute when used instead of native<th>.- Layout tables use
role="presentation"(or live inside a presentation subtree) and are excluded from scoring. - Tables with only header rows and no visible
<td>are skipped (nothing to associate).
Failing signals
.forge-table-wraptable with<thead><tr><th>…</th>but noscopeon any<th>, and body<td>cells withoutheaders—issueth-missing-scope, major.- Data grid built with
<td>in the first row acting as visual headers (no<th>) and noheadersattributes—issueno-header-cells, major. - Showcase copy-paste from early
Srfexamples: striped.tablewith header text in<th>but missingscope="col". - Status / heatmap tables beside
ks-chart-mountblocks where color encodes meaning but column titles are plain<td>or unscoped<th>. - Third-party widget tables mounted in
.forge-widget-hostwithout thead scope after hydration (common regression when only CSS classes are applied). - Visible comparison tables inside expanded panels fail when they contain data cells and lack scope or
headerswiring.
Before example
Before (failing example)
Tables
Wrap standard Bootstrap tables in .forge-table-wrap for themed styling.
| Methodology | Type | Team size | Iteration |
|---|---|---|---|
| Scrum | Agile | 5–9 | 2-week sprint |
| Kanban | Lean | Any | Continuous |
| Forge | AI-native | 1–3 | Spark-driven |
Failing KS markup: surfaces-style table with real data cells but header cells lack scope and body cells lack headers (matches legacy generator/pages/surfaces.py pattern).
After example
After (passing example)
Tables
Wrap standard Bootstrap tables in .forge-table-wrap for themed styling.
| Methodology | Type | Team size | Iteration |
|---|---|---|---|
| Scrum | Agile | 5–9 | 2-week sprint |
| Kanban | Lean | Any | Continuous |
| Forge | AI-native | 1–3 | Spark-driven |
Passing KS markup: same surfaces table with scope="col" on headers (matches render_table and controls.py).
Alternative passing pattern using headers when scope is impractical (sparse matrix beside a chart):
<section id="sec-dc-kinds" class="ks-section" hash="Dcs" data-ks-hash="Dcs" data-ks-type="page" data-ks-name="data-charts-static">
<h2 class="ks-section-title">Weekly commits</h2>
<div class="ks-chart-mount mb-3" data-ks-chart data-ks-chart-kind="commit_weekly" data-ks-chart-state="ready"></div>
<div class="forge-table-wrap">
<table class="table table-sm table-striped mb-0" id="commit-weekly-grid">
<thead>
<tr>
<th id="cw-week" scope="col">ISO week</th>
<th id="cw-count" scope="col">Commits</th>
</tr>
</thead>
<tbody>
<tr>
<td headers="cw-week">2025-W08</td>
<td headers="cw-count">4</td>
</tr>
<tr>
<td headers="cw-week">2025-W09</td>
<td headers="cw-count">6</td>
</tr>
</tbody>
</table>
</div>
</section>
Evidence and remediation
Evidence: Auditor metrics tableHeadersReport with violations[] entries kind: missing-table-headers, issue of no-header-cells or th-missing-scope, plus selectorHint (for example table.table.table-striped[@Srf]), dataCellCount, and headerCount. Findings surface as major accessibility issues with evidence token missing_table_headers. Capture the table outer HTML, each <th> scope, and any headers on <td> cells.
Remediate (in order):
- Prefer
render_table(headers, rows)for new tables so KS emits<th scope='col'>inside.forge-table-wrap. - For hand-authored HTML, add
scope="col"to every column header in<thead>(orscope="row"for row-header columns). - When headers are not
<th>(legacy markup), setheaders="id1 id2"on each<td>referencing in-table header element ids. - Mark layout-only grids with
role="presentation"; do not use presentation role on data tables. - Re-run deterministic table-header checks; pair with
DET.DATA.COLOR_ONLYwhen cells use background fills andDET.CHART.ALT_SUMMARYfor adjacent chart mounts.