Skip to content

Commit e713bdd

Browse files
committed
fix(template-no-empty-headings): align aria-hidden handling with WAI-ARIA spec
Per WAI-ARIA 1.2 §6.6, `aria-hidden` has value type true/false/undefined with default `undefined`. Per §8.5, missing or empty-string attribute values resolve to the default. So a valueless `aria-hidden` is NOT hidden per spec — only an explicit `"true"` (ASCII case-insensitive per HTML enumerated-attribute rules) hides the element. The earlier direction of this PR borrowed the HTML boolean-attribute intuition (presence = truthy) from jsx-a11y. That's a peer-plugin convention, not a spec mandate — aria-hidden is an enumerated ARIA attribute, not a boolean HTML one. vue-a11y's heading-has-content doesn't exempt aria-hidden headings at all; lit-a11y has the inverse rule. Behaviour now: - Exempt (hidden): `aria-hidden="true"` / "TRUE" / "True", `{{true}}`, `{{"true"}}` / case-variants. - Flag (NOT hidden per spec): valueless `<h1 aria-hidden>`, empty `<h1 aria-hidden="">`, `{{false}}`, `{{"false"}}`, `"false"`.
1 parent 872190f commit e713bdd

2 files changed

Lines changed: 20 additions & 18 deletions

File tree

lib/rules/template-no-empty-headings.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
const HEADINGS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']);
22

3+
// Per WAI-ARIA 1.2 §6.6 and §8.5 (https://www.w3.org/TR/wai-aria-1.2/#aria-hidden),
4+
// aria-hidden has value type true/false/undefined with DEFAULT `undefined`, and
5+
// missing / empty-string values resolve to that default. So a valueless
6+
// `aria-hidden` is NOT hidden per spec — only an explicit `"true"` (ASCII
7+
// case-insensitive, per the enumerated-attribute rules) hides the element.
38
function isAriaHiddenTruthy(attr) {
4-
if (!attr) {
9+
const value = attr?.value;
10+
if (!value) {
511
return false;
612
}
7-
const value = attr.value;
8-
// Valueless or empty-string attribute — <h1 aria-hidden />. Per HTML boolean
9-
// attribute semantics (and jsx-a11y/vue-a11y convention), presence = truthy.
10-
if (!value || (value.type === 'GlimmerTextNode' && value.chars === '')) {
11-
return true;
12-
}
13-
// HTML attribute values compare ASCII-case-insensitively, so "TRUE"/"True"
14-
// count as truthy.
1513
if (value.type === 'GlimmerTextNode') {
1614
return value.chars.toLowerCase() === 'true';
1715
}

tests/lib/rules/template-no-empty-headings.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,8 @@ ruleTester.run('template-no-empty-headings', rule, {
4444
'<template><h2><@heading /></h2></template>',
4545
'<template><h3><ns.Heading /></h3></template>',
4646

47-
// aria-hidden as a boolean / valueless / empty / mustache attribute — all
48-
// should exempt the heading (aligns with jsx-a11y / vue-a11y treatment of
49-
// boolean HTML attributes).
50-
'<template><h1 aria-hidden></h1></template>',
51-
'<template><h1 aria-hidden=""></h1></template>',
5247
'<template><h1 aria-hidden={{true}}></h1></template>',
5348
'<template><h1 aria-hidden="true">Visible to sighted only</h1></template>',
54-
55-
// HTML attribute values are ASCII case-insensitive — "TRUE" / "True" /
56-
// {{"TRUE"}} / {{"True"}} all count as truthy.
5749
'<template><h1 aria-hidden="TRUE"></h1></template>',
5850
'<template><h1 aria-hidden="True"></h1></template>',
5951
'<template><h1 aria-hidden={{"TRUE"}}></h1></template>',
@@ -148,7 +140,19 @@ ruleTester.run('template-no-empty-headings', rule, {
148140
errors: [{ messageId: 'emptyHeading' }],
149141
},
150142

151-
// Falsy mustache aria-hidden values do not exempt the heading.
143+
// Valueless / empty aria-hidden resolves to the default `undefined` per
144+
// WAI-ARIA §6.6 — the element is NOT hidden, so an otherwise-empty heading
145+
// still flags.
146+
{
147+
code: '<template><h1 aria-hidden></h1></template>',
148+
output: null,
149+
errors: [{ messageId: 'emptyHeading' }],
150+
},
151+
{
152+
code: '<template><h1 aria-hidden=""></h1></template>',
153+
output: null,
154+
errors: [{ messageId: 'emptyHeading' }],
155+
},
152156
{
153157
code: '<template><h1 aria-hidden={{false}}></h1></template>',
154158
output: null,

0 commit comments

Comments
 (0)