ReviewOS

stacks/stx

stx a11y flags a role="presentation" layout table for missing <th> — the correct markup is what triggers it

#1916
Closed glennmichael123 opened this 14 days ago · 1 comment
14 days ago

Summary

stx a11y reports "Table missing header cells (th)" for a <table role="presentation">. That role is precisely the declaration that the table is layout, not data — it removes the table semantics from the accessibility tree, so <th> would be wrong to add.

This is the last finding in an app that is otherwise at zero, and it is unfixable by the author: the correct markup is what triggers it.

Reproduction

mail.stx:

<table role="presentation" cellpadding="0" cellspacing="0" style="width: 100%;">
  <tr><td>Layout cell</td></tr>
</table>
$ stx a11y .
  • SERIOUS: Table missing header cells (th)
    Use th elements for table headers
    Element: <table role="presentation" cellpadding="0" cellspacing="0" style="width: 100%;">

Adding <th> to satisfy the rule would reintroduce the semantics the role exists to remove.

Why it shows up in a real app

Email templates. Every HTML email is nested layout tables — mail clients strip <style> and have no reliable box model, so role="presentation" on layout tables is the standard, correct pattern. Our one email template accounts for 3 of 3 remaining findings.

Worth noting the rule is right to exist: a data table with no <th> is a real problem. It just needs to skip tables that have declared themselves presentational.

Suggested fix

Skip the check when the table carries role="presentation" or role="none" (the two are synonyms in ARIA). Both should be honoured.

A test asserting that a role="presentation" table produces no finding, and that an otherwise identical table without the role still does, would pin it in both directions.

Context

stx a11y is advisory — it exits 0 regardless — so this is not blocking a gate. It matters because an advisory checker with a permanent, uncloseable finding is one an app learns to ignore, which is the same failure the handoff describes for a gate that invents errors.

14 days ago

Fixed in c5965f1.

role="presentation" and role="none" now suppress both the header and the caption finding. You called the caption rule out only implicitly — it sits beside the header rule and had the identical blind spot, so a layout table was being asked for a <caption> describing semantics it had just declared it doesn't have.

Pinned in both directions exactly as you asked: the role suppresses the finding, and an otherwise-identical table without it still produces one. The rule is right to exist and I didn't weaken it — it just takes the author's word for which kind of table it is now.

One thing worth knowing: there were two implementations of this rule. checkA11y runs a regex path when no DOM is available — which is what the CLI does — and a DOM path when one is. Two implementations of one rule go wrong by disagreeing, so both now call a single isPresentationalRole predicate, and the tests exercise both paths explicitly rather than whichever one the test environment happens to select. (happy-dom is preloaded in this repo's tests, so a test calling checkA11y would only ever have covered the DOM path — and the CLI uses the other one.)

The predicate reads the first token of the role attribute, since it takes a space-separated fallback list and the first recognised token wins: role="presentation table" is presentational, role="table presentation" is not.

A correction on the framing: stx a11y isn't advisory — it exits 1 when it finds issues. Your email template was failing that command, not just producing noise in it. That makes the uncloseable-finding problem worse than you described, not better.

Related: #1918 was fixed in the same commit, and the two interact. If you'd been pointing that command at a file path or a glob rather than a directory, it would have reported your email template clean regardless of this rule.

Verification

9 tests. Sabotage-checked per path: removing the role check from the regex path fails 2, removing it from the DOM path fails 3 including the caption case.

Full suite 11857 pass; the 4 failures on that run reproduce on a clean checkout of HEAD and are unrelated.

Sign in to comment on this issue.