Rule handbook metadata

Rule
DET.DATA.TABLE_HEADERS · lane deterministic
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.py copies the After example HTML from this handbook page onto the defect fixture, then invoke-det-ruleset-remediation-verify.sh re-audits and expects zero findings for this rule.
  • Production sites: run-website-ux-remediation-loop.sh runs handbook_html_patch (HTML patches in lib/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_table output wraps tables in .forge-table-wrap with <th scope='col'> for every column header (canonical KS helper).
  • Hand-authored tables follow Ctr / controls.py: .forge-table-wrap > .table.table-sm with <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 valid scope attribute 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-wrap table with <thead><tr><th>…</th> but no scope on any <th>, and body <td> cells without headers—issue th-missing-scope, major.
  • Data grid built with <td> in the first row acting as visual headers (no <th>) and no headers attributes—issue no-header-cells, major.
  • Showcase copy-paste from early Srf examples: striped .table with header text in <th> but missing scope="col".
  • Status / heatmap tables beside ks-chart-mount blocks where color encodes meaning but column titles are plain <td> or unscoped <th>.
  • Third-party widget tables mounted in .forge-widget-host without 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 headers wiring.

Before example

Before (failing example)

Tables

Wrap standard Bootstrap tables in .forge-table-wrap for themed styling.

Methodology Type Team size Iteration
ScrumAgile5–92-week sprint
KanbanLeanAnyContinuous
ForgeAI-native1–3Spark-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
ScrumAgile5–92-week sprint
KanbanLeanAnyContinuous
ForgeAI-native1–3Spark-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):

  1. Prefer render_table(headers, rows) for new tables so KS emits <th scope='col'> inside .forge-table-wrap.
  2. For hand-authored HTML, add scope="col" to every column header in <thead> (or scope="row" for row-header columns).
  3. When headers are not <th> (legacy markup), set headers="id1 id2" on each <td> referencing in-table header element ids.
  4. Mark layout-only grids with role="presentation"; do not use presentation role on data tables.
  5. Re-run deterministic table-header checks; pair with DET.DATA.COLOR_ONLY when cells use background fills and DET.CHART.ALT_SUMMARY for adjacent chart mounts.