@surea11y/core 1.7.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +103 -1
- package/README.md +157 -54
- package/docs/ACT_RULE_MAPPING.md +8 -7
- package/docs/API_STABILITY.md +18 -5
- package/docs/BINDING_AUTHORS_GUIDE.md +2 -2
- package/docs/CI_INTEGRATIONS.md +43 -0
- package/docs/DESIGN_CHALLENGES.md +97 -3
- package/docs/EARL.md +2 -2
- package/docs/ENGINE_OPTIONS.md +81 -3
- package/docs/I18N.md +62 -20
- package/docs/JUNIT.md +73 -0
- package/docs/LIMITATIONS.md +1 -0
- package/docs/OUTPUT_SCHEMA.md +19 -6
- package/docs/REPORT.md +7 -2
- package/docs/RULE_AUTHORING.md +73 -6
- package/docs/RULE_CATALOG.md +139 -116
- package/docs/RULE_EXAMPLES.md +2189 -0
- package/docs/RULE_HELPERS.md +62 -5
- package/docs/RULE_TAXONOMY.md +2 -2
- package/docs/SARIF.md +2 -1
- package/docs/WCAG_CONFORMANCE.md +56 -3
- package/package.json +34 -11
- package/profiles/index.js +14 -0
- package/src/checks/automatic/area-alt-present.js +87 -31
- package/src/checks/automatic/aria-braille-equivalent.js +25 -7
- package/src/checks/automatic/aria-hidden-focus.js +74 -18
- package/src/checks/automatic/aria-prohibited-attr.js +17 -4
- package/src/checks/automatic/aria-required-attr.js +29 -0
- package/src/checks/automatic/aria-role-name-present.js +19 -2
- package/src/checks/automatic/aria-valid-attr-value.js +28 -16
- package/src/checks/automatic/autocomplete-valid.js +26 -11
- package/src/checks/automatic/avoid-inline-spacing.js +105 -40
- package/src/checks/automatic/button-name-present.js +2 -1
- package/src/checks/automatic/canvas-text-alternative-present.js +105 -14
- package/src/checks/automatic/combobox-name-present.js +34 -51
- package/src/checks/automatic/contrast-computable.js +35 -4
- package/src/checks/automatic/contrast-enhanced.js +4 -4
- package/src/checks/automatic/contrast-minimum.js +45 -11
- package/src/checks/automatic/css-orientation-lock.js +152 -30
- package/src/checks/automatic/definition-list-children-valid.js +67 -23
- package/src/checks/automatic/deprecated-elements-not-used.js +43 -38
- package/src/checks/automatic/dialog-name-present.js +28 -9
- package/src/checks/automatic/duplicate-id.js +6 -2
- package/src/checks/automatic/identical-iframes-same-purpose.js +4 -4
- package/src/checks/automatic/iframe-focusable-content.js +7 -4
- package/src/checks/automatic/iframe-title-unique.js +36 -81
- package/src/checks/automatic/input-image-alt-present.js +32 -20
- package/src/checks/automatic/label-in-name.js +40 -13
- package/src/checks/automatic/language-page-present.js +12 -6
- package/src/checks/automatic/link-in-text-block.js +272 -60
- package/src/checks/automatic/link-name-present.js +13 -5
- package/src/checks/automatic/list-children-valid.js +18 -1
- package/src/checks/automatic/listbox-name-present.js +19 -49
- package/src/checks/automatic/listitem-parent-valid.js +4 -3
- package/src/checks/automatic/meta-refresh-no-exceptions.js +3 -3
- package/src/checks/automatic/page-title-present.js +16 -4
- package/src/checks/automatic/progressbar-name-present.js +11 -1
- package/src/checks/automatic/role-img-text-alternative-present.js +9 -5
- package/src/checks/automatic/searchbox-name-present.js +32 -49
- package/src/checks/automatic/server-side-image-map-absent.js +48 -28
- package/src/checks/automatic/slider-name-present.js +38 -52
- package/src/checks/automatic/spinbutton-name-present.js +32 -49
- package/src/checks/automatic/target-size-minimum.js +0 -11
- package/src/checks/automatic/td-has-header.js +41 -5
- package/src/checks/automatic/text-spacing-content-loss.js +548 -0
- package/src/checks/automatic/textbox-name-present.js +32 -49
- package/src/checks/automatic/valid-lang.js +15 -10
- package/src/checks/manual/area-alt-quality-manual.js +113 -31
- package/src/checks/manual/canvas-text-alternative-quality-manual.js +0 -2
- package/src/checks/manual/css-focus-indicator-suppressed-manual.js +23 -10
- package/src/checks/manual/css-hidden-focus.js +215 -7
- package/src/checks/manual/form-control-label-quality-manual.js +109 -5
- package/src/checks/manual/heading-order-manual.js +9 -1
- package/src/checks/manual/heading-quality-manual.js +143 -9
- package/src/checks/manual/img-alt-decorative-manual.js +6 -3
- package/src/checks/manual/input-image-alt-quality-manual.js +81 -13
- package/src/checks/manual/link-name-quality-manual.js +130 -4
- package/src/checks/manual/media-transcript-present-manual.js +65 -8
- package/src/checks/manual/mouse-only-event-handlers-manual.js +66 -5
- package/src/checks/manual/no-autoplay-audio-manual.js +93 -6
- package/src/checks/manual/p-as-heading-manual.js +89 -44
- package/src/checks/manual/page-title-patterns-manual.js +77 -8
- package/src/checks/manual/skip-link-manual.js +42 -14
- package/src/checks/manual/table-fake-caption-manual.js +32 -1
- package/src/checks/manual/video-caption-manual.js +47 -24
- package/src/checks/manual-review.js +0 -4
- package/src/core.js +14285 -2219
- package/src/coverage/en301549-map.js +187 -0
- package/src/coverage/standards.js +279 -0
- package/src/coverage/wcag-facets.js +1119 -0
- package/src/coverage/wcag-version-map.js +101 -0
- package/src/en301549.js +33 -0
- package/src/junit.js +321 -0
- package/src/profile-kit.js +163 -0
- package/src/report.js +343 -74
- package/src/sarif.js +34 -3
- package/src/wcag.js +105 -0
- package/surea11y.browser.js +5 -4
- package/surea11y.i18n.de.js +1 -1
- package/surea11y.i18n.es.js +1 -1
- package/surea11y.i18n.fr.js +1 -1
- package/surea11y.i18n.ja.js +3 -0
- package/src/checks/manual/area-alt-decorative-manual.js +0 -255
|
@@ -0,0 +1,2189 @@
|
|
|
1
|
+
# Rule examples
|
|
2
|
+
|
|
3
|
+
Hand-authored `Passed`/`Failed` (or, for manual rules, `Flagged`/`Not applicable`)
|
|
4
|
+
example pairs for every one of core's rules, meant to feed a future rule-page
|
|
5
|
+
docs site the way alfa.siteimprove.com/rules pages show worked examples
|
|
6
|
+
alongside a rule's description. Companion to [`RULE_CATALOG.md`](./RULE_CATALOG.md),
|
|
7
|
+
which carries each rule's title, WCAG mapping, applicability, and expectation —
|
|
8
|
+
this file adds one illustrative pair per rule, plus an extra `Flagged (cantTell)`
|
|
9
|
+
example where a rule's outcome is tiered. A profile's rules have theirs in the
|
|
10
|
+
profile.
|
|
11
|
+
|
|
12
|
+
Every example was verified against the built engine (`npm run build`, then
|
|
13
|
+
replayed through `tests/helpers/runa11yCoreOnHtml`), not just read off a
|
|
14
|
+
fixture's `.case-title` label or guessed from the rule's prose. Draft content:
|
|
15
|
+
hand-curated, not generated, and not yet checked by CI the way
|
|
16
|
+
`RULE_CATALOG.md` is. Once this feeds a real generator, it should get the
|
|
17
|
+
same treatment as the fixture-marker check (`scripts/generate-fixture-markers.js`):
|
|
18
|
+
a `--check` script wired into CI so a new or changed rule can't silently ship
|
|
19
|
+
without a matching example.
|
|
20
|
+
|
|
21
|
+
Manual rules (`type: 'manual'`) are capped at `cantTell`/`notApplicable` and
|
|
22
|
+
never return `pass`/`fail`; their pair below is `Flagged (cantTell)` /
|
|
23
|
+
`Not applicable` instead of `Passed`/`Failed`. A few automatic rules also
|
|
24
|
+
never reach `fail` in practice — each says so where it applies.
|
|
25
|
+
|
|
26
|
+
## accesskeys
|
|
27
|
+
|
|
28
|
+
**Flagged (cantTell)**
|
|
29
|
+
```html
|
|
30
|
+
<a href="/s1" accesskey="s">S1</a>
|
|
31
|
+
<a href="/s2" accesskey="s">S2</a>
|
|
32
|
+
```
|
|
33
|
+
Two elements share the same `accesskey` value ("s"), which most browsers resolve unpredictably.
|
|
34
|
+
|
|
35
|
+
**Not applicable**
|
|
36
|
+
```html
|
|
37
|
+
<a href="/a" accesskey="a">A</a>
|
|
38
|
+
<a href="/b" accesskey="b">B</a>
|
|
39
|
+
```
|
|
40
|
+
Every `accesskey` value on the page is unique.
|
|
41
|
+
|
|
42
|
+
## area-alt-present
|
|
43
|
+
|
|
44
|
+
**Passed**
|
|
45
|
+
```html
|
|
46
|
+
<img src="image.png" alt="" usemap="#nav">
|
|
47
|
+
<map name="nav">
|
|
48
|
+
<area href="/x" shape="rect" coords="0,0,40,24" alt="Go to green">
|
|
49
|
+
</map>
|
|
50
|
+
```
|
|
51
|
+
The `<area>` is a link inside a used map and has a non-empty `alt`.
|
|
52
|
+
|
|
53
|
+
**Failed**
|
|
54
|
+
```html
|
|
55
|
+
<img src="image.png" alt="" usemap="#nav">
|
|
56
|
+
<map name="nav">
|
|
57
|
+
<area href="/x" shape="rect" coords="0,0,40,24">
|
|
58
|
+
</map>
|
|
59
|
+
```
|
|
60
|
+
The `<area>` is a link (has `href`) in a used map but has no `alt`.
|
|
61
|
+
|
|
62
|
+
## area-alt-quality
|
|
63
|
+
|
|
64
|
+
*Manual, and like its siblings below, flags every applicable element with a detected text alternative for review — it isn't selective about which ones look wrong. The flagged example shows a case where review genuinely matters, not a case the rule specifically detected as bad.*
|
|
65
|
+
|
|
66
|
+
**Flagged (cantTell)**
|
|
67
|
+
```html
|
|
68
|
+
<img src="office-map.png" usemap="#floorplan">
|
|
69
|
+
<map name="floorplan">
|
|
70
|
+
<area href="/rooms/12" shape="rect" coords="0,0,40,24" alt="Room 12">
|
|
71
|
+
</map>
|
|
72
|
+
```
|
|
73
|
+
The area has a non-empty text alternative in a used image map, worth confirming "Room 12" is accurate for that region. An `aria-label`, `aria-labelledby` or `title` is asked about the same way, and each occurrence lists the sources it found.
|
|
74
|
+
|
|
75
|
+
**Not applicable**
|
|
76
|
+
```html
|
|
77
|
+
<map name="unused">
|
|
78
|
+
<area href="/rooms/12" shape="rect" coords="0,0,40,24" alt="Room 12">
|
|
79
|
+
</map>
|
|
80
|
+
```
|
|
81
|
+
No `<img usemap>` references this map, so the area is never hit-tested or rendered — out of scope.
|
|
82
|
+
|
|
83
|
+
## aria-allowed-attr
|
|
84
|
+
|
|
85
|
+
**Passed**
|
|
86
|
+
```html
|
|
87
|
+
<div role="checkbox" aria-checked="true"></div>
|
|
88
|
+
```
|
|
89
|
+
`aria-checked` is one of the states role="checkbox" supports.
|
|
90
|
+
|
|
91
|
+
**Failed**
|
|
92
|
+
```html
|
|
93
|
+
<div role="checkbox" aria-valuenow="1"></div>
|
|
94
|
+
```
|
|
95
|
+
`aria-valuenow` belongs to range widgets, not role="checkbox".
|
|
96
|
+
|
|
97
|
+
## aria-allowed-role
|
|
98
|
+
|
|
99
|
+
*Despite being `type: 'automatic'`, this rule never returns `fail` — no ACT rule and no WCAG mapping exist for it, so a disallowed role is raised for review rather than decided outright.*
|
|
100
|
+
|
|
101
|
+
**Passed**
|
|
102
|
+
```html
|
|
103
|
+
<nav role="navigation">...</nav>
|
|
104
|
+
```
|
|
105
|
+
`role="navigation"` is permitted on `<nav>` by the ARIA-in-HTML spec (it's also the element's implicit role).
|
|
106
|
+
|
|
107
|
+
**Flagged (cantTell)**
|
|
108
|
+
```html
|
|
109
|
+
<nav role="tab">...</nav>
|
|
110
|
+
```
|
|
111
|
+
`role="tab"` is not one of the roles ARIA-in-HTML permits on `<nav>`.
|
|
112
|
+
|
|
113
|
+
## aria-braille-equivalent
|
|
114
|
+
|
|
115
|
+
**Passed**
|
|
116
|
+
```html
|
|
117
|
+
<button aria-braillelabel="SAVE">Save changes</button>
|
|
118
|
+
```
|
|
119
|
+
The braille label supplements a real accessible name ("Save changes"); non-braille assistive technology still gets a name.
|
|
120
|
+
|
|
121
|
+
**Flagged (cantTell)**
|
|
122
|
+
```html
|
|
123
|
+
<button aria-braillelabel="SAVE"></button>
|
|
124
|
+
```
|
|
125
|
+
`aria-braillelabel` is the only naming mechanism present; non-braille assistive technology gets no accessible name at all.
|
|
126
|
+
|
|
127
|
+
## aria-checked-state-mismatch
|
|
128
|
+
|
|
129
|
+
**Flagged (cantTell)**
|
|
130
|
+
```html
|
|
131
|
+
<input type="checkbox" checked aria-checked="false">
|
|
132
|
+
```
|
|
133
|
+
The checkbox is actually checked, but `aria-checked="false"` tells assistive technology the opposite.
|
|
134
|
+
|
|
135
|
+
**Not applicable**
|
|
136
|
+
```html
|
|
137
|
+
<input type="checkbox" checked aria-checked="true">
|
|
138
|
+
```
|
|
139
|
+
`aria-checked` matches the control's real checked state.
|
|
140
|
+
|
|
141
|
+
## aria-conditional-attr
|
|
142
|
+
|
|
143
|
+
**Passed**
|
|
144
|
+
```html
|
|
145
|
+
<input type="text" aria-invalid="true" aria-errormessage="err1">
|
|
146
|
+
<span id="err1">Enter a valid email address.</span>
|
|
147
|
+
```
|
|
148
|
+
`aria-invalid="true"` means `aria-errormessage` is actually exposed to assistive technology.
|
|
149
|
+
|
|
150
|
+
**Flagged (cantTell)**
|
|
151
|
+
```html
|
|
152
|
+
<input type="text" aria-errormessage="err1">
|
|
153
|
+
<span id="err1">Enter a valid email address.</span>
|
|
154
|
+
```
|
|
155
|
+
Without `aria-invalid` set to a non-`"false"` value, `aria-errormessage` is never exposed — the reference is currently inert.
|
|
156
|
+
|
|
157
|
+
## aria-deprecated-role
|
|
158
|
+
|
|
159
|
+
**Passed**
|
|
160
|
+
```html
|
|
161
|
+
<ul role="list"><li>Item</li></ul>
|
|
162
|
+
```
|
|
163
|
+
`role="list"` is current, non-deprecated ARIA.
|
|
164
|
+
|
|
165
|
+
**Flagged (cantTell)**
|
|
166
|
+
```html
|
|
167
|
+
<div role="directory"><a href="/a">Item</a></div>
|
|
168
|
+
```
|
|
169
|
+
`role="directory"` is deprecated in WAI-ARIA in favor of `role="list"`; still valid markup, but discouraged.
|
|
170
|
+
|
|
171
|
+
## aria-hidden-body
|
|
172
|
+
|
|
173
|
+
**Passed**
|
|
174
|
+
```html
|
|
175
|
+
<body>Hi</body>
|
|
176
|
+
```
|
|
177
|
+
The document `<body>` is not `aria-hidden`.
|
|
178
|
+
|
|
179
|
+
**Failed**
|
|
180
|
+
```html
|
|
181
|
+
<body aria-hidden="true">Hi</body>
|
|
182
|
+
```
|
|
183
|
+
Hiding `<body>` removes the entire page from the accessibility tree at once.
|
|
184
|
+
|
|
185
|
+
## aria-hidden-focus
|
|
186
|
+
|
|
187
|
+
**Passed**
|
|
188
|
+
```html
|
|
189
|
+
<div aria-hidden="true">
|
|
190
|
+
<p>Decorative text</p>
|
|
191
|
+
</div>
|
|
192
|
+
```
|
|
193
|
+
Nothing inside the hidden subtree is focusable, so hiding it from assistive tech leaves no dead focus stop.
|
|
194
|
+
|
|
195
|
+
**Failed**
|
|
196
|
+
```html
|
|
197
|
+
<div aria-hidden="true">
|
|
198
|
+
<a href="/x">Link</a>
|
|
199
|
+
</div>
|
|
200
|
+
```
|
|
201
|
+
The link stays in the tab order even though `aria-hidden` removes it from the accessibility tree.
|
|
202
|
+
|
|
203
|
+
## aria-prohibited-attr
|
|
204
|
+
|
|
205
|
+
**Passed**
|
|
206
|
+
```html
|
|
207
|
+
<div role="generic"></div>
|
|
208
|
+
```
|
|
209
|
+
role="generic" cannot be named, and none of the naming attributes are present.
|
|
210
|
+
|
|
211
|
+
**Failed**
|
|
212
|
+
```html
|
|
213
|
+
<div role="generic" aria-label="Something"></div>
|
|
214
|
+
```
|
|
215
|
+
`aria-label` is set on a role that WAI-ARIA prohibits from being named.
|
|
216
|
+
|
|
217
|
+
## aria-prohibited-children
|
|
218
|
+
|
|
219
|
+
**Passed**
|
|
220
|
+
```html
|
|
221
|
+
<ul role="menubar">
|
|
222
|
+
<li role="menuitem">File</li>
|
|
223
|
+
<li role="menuitem">Edit</li>
|
|
224
|
+
</ul>
|
|
225
|
+
```
|
|
226
|
+
Both owned children are roles menubar's spec allows.
|
|
227
|
+
|
|
228
|
+
**Failed**
|
|
229
|
+
```html
|
|
230
|
+
<div role="table">
|
|
231
|
+
<div role="row"><div role="cell">x</div></div>
|
|
232
|
+
<div role="button">Not a valid table child</div>
|
|
233
|
+
</div>
|
|
234
|
+
```
|
|
235
|
+
role="table" only allows row (or rowgroup) children; the button is a disallowed owned child.
|
|
236
|
+
|
|
237
|
+
## aria-required-attr
|
|
238
|
+
|
|
239
|
+
**Passed**
|
|
240
|
+
```html
|
|
241
|
+
<div role="checkbox" aria-checked="true"></div>
|
|
242
|
+
```
|
|
243
|
+
role="checkbox" requires `aria-checked`, and it's present.
|
|
244
|
+
|
|
245
|
+
**Failed**
|
|
246
|
+
```html
|
|
247
|
+
<div role="checkbox"></div>
|
|
248
|
+
```
|
|
249
|
+
role="checkbox" requires `aria-checked`; it's missing entirely.
|
|
250
|
+
|
|
251
|
+
## aria-required-children
|
|
252
|
+
|
|
253
|
+
**Passed**
|
|
254
|
+
```html
|
|
255
|
+
<ul role="list"><li>Item</li></ul>
|
|
256
|
+
```
|
|
257
|
+
The list role owns at least one `listitem`-role child.
|
|
258
|
+
|
|
259
|
+
**Flagged (cantTell)**
|
|
260
|
+
```html
|
|
261
|
+
<div role="list"></div>
|
|
262
|
+
```
|
|
263
|
+
An empty `role="list"` owns no required child role — announced as a list with no items, which is what it is, but worth a look.
|
|
264
|
+
|
|
265
|
+
## aria-required-parent
|
|
266
|
+
|
|
267
|
+
**Passed**
|
|
268
|
+
```html
|
|
269
|
+
<div role="tablist">
|
|
270
|
+
<div role="tab">Overview</div>
|
|
271
|
+
</div>
|
|
272
|
+
```
|
|
273
|
+
role="tab" requires an ancestor (or `aria-owns` owner) with role="tablist", and one is present.
|
|
274
|
+
|
|
275
|
+
**Failed**
|
|
276
|
+
```html
|
|
277
|
+
<nav role="tab"></nav>
|
|
278
|
+
```
|
|
279
|
+
role="tab" has no tablist ancestor or owner anywhere on the page.
|
|
280
|
+
|
|
281
|
+
## aria-role-name-present
|
|
282
|
+
|
|
283
|
+
**Passed**
|
|
284
|
+
```html
|
|
285
|
+
<div role="grid" aria-label="Orders"></div>
|
|
286
|
+
```
|
|
287
|
+
role="grid" requires an accessible name, and `aria-label` provides one.
|
|
288
|
+
|
|
289
|
+
**Failed**
|
|
290
|
+
```html
|
|
291
|
+
<div role="grid"></div>
|
|
292
|
+
```
|
|
293
|
+
role="grid" requires an accessible name; none is present.
|
|
294
|
+
|
|
295
|
+
## aria-roles-valid
|
|
296
|
+
|
|
297
|
+
**Passed**
|
|
298
|
+
```html
|
|
299
|
+
<div role="button">Click</div>
|
|
300
|
+
```
|
|
301
|
+
"button" is a real, concrete ARIA role.
|
|
302
|
+
|
|
303
|
+
**Failed**
|
|
304
|
+
```html
|
|
305
|
+
<div role="buton"></div>
|
|
306
|
+
```
|
|
307
|
+
"buton" isn't a defined ARIA role token (typo).
|
|
308
|
+
|
|
309
|
+
## aria-text
|
|
310
|
+
|
|
311
|
+
**Flagged (cantTell)**
|
|
312
|
+
```html
|
|
313
|
+
<span role="text">Some text with <a href="#">a link</a> inside it.</span>
|
|
314
|
+
```
|
|
315
|
+
`role="text"` tells assistive technology to treat this as a single flat string, but the nested link is still focusable and interactive underneath it.
|
|
316
|
+
|
|
317
|
+
**Not applicable**
|
|
318
|
+
```html
|
|
319
|
+
<span role="text">Plain text with no interactive content.</span>
|
|
320
|
+
```
|
|
321
|
+
No focusable descendant exists inside the `role="text"` element.
|
|
322
|
+
|
|
323
|
+
## aria-valid-attr
|
|
324
|
+
|
|
325
|
+
*Despite being `type: 'automatic'`, this rule never returns `fail` — an aria-* attribute the spec doesn't define is inert, so nothing about the element's exposed name, role, or value actually changes.*
|
|
326
|
+
|
|
327
|
+
**Passed**
|
|
328
|
+
```html
|
|
329
|
+
<button aria-label="Save">Save</button>
|
|
330
|
+
```
|
|
331
|
+
`aria-label` is a real, defined ARIA attribute.
|
|
332
|
+
|
|
333
|
+
**Flagged (cantTell)**
|
|
334
|
+
```html
|
|
335
|
+
<button aria-labeledby="x">Save</button>
|
|
336
|
+
```
|
|
337
|
+
`aria-labeledby` is a typo (the real attribute is `aria-labelledby`) and is silently ignored by assistive technology.
|
|
338
|
+
|
|
339
|
+
## aria-valid-attr-value
|
|
340
|
+
|
|
341
|
+
**Passed**
|
|
342
|
+
```html
|
|
343
|
+
<div aria-hidden="true"></div>
|
|
344
|
+
```
|
|
345
|
+
"true" is a valid value for the boolean-typed `aria-hidden`.
|
|
346
|
+
|
|
347
|
+
**Failed**
|
|
348
|
+
```html
|
|
349
|
+
<div aria-hidden="yes"></div>
|
|
350
|
+
```
|
|
351
|
+
`aria-hidden` is boolean-typed; "yes" isn't a valid boolean token.
|
|
352
|
+
|
|
353
|
+
## autocomplete-valid
|
|
354
|
+
|
|
355
|
+
**Passed**
|
|
356
|
+
```html
|
|
357
|
+
<input autocomplete="email">
|
|
358
|
+
```
|
|
359
|
+
"email" is a recognized autofill field-name token.
|
|
360
|
+
|
|
361
|
+
**Failed**
|
|
362
|
+
```html
|
|
363
|
+
<input autocomplete="emial">
|
|
364
|
+
```
|
|
365
|
+
"emial" is a typo, not a recognized autofill token.
|
|
366
|
+
|
|
367
|
+
## avoid-inline-spacing
|
|
368
|
+
|
|
369
|
+
**Passed**
|
|
370
|
+
```html
|
|
371
|
+
<p style="letter-spacing:2px !important">Spaced text</p>
|
|
372
|
+
```
|
|
373
|
+
The forced letter-spacing is above the WCAG 1.4.12 minimum metric.
|
|
374
|
+
|
|
375
|
+
**Failed**
|
|
376
|
+
```html
|
|
377
|
+
<p style="line-height:1.2 !important">The toy brought back fond memories of being lost in the rain forest.</p>
|
|
378
|
+
```
|
|
379
|
+
`!important` forces line-height below the 1.5 minimum, and the page's own styles can't override it.
|
|
380
|
+
|
|
381
|
+
**Flagged (cantTell)**
|
|
382
|
+
```html
|
|
383
|
+
<p style="line-height:1 !important">Opening hours</p>
|
|
384
|
+
```
|
|
385
|
+
The text is short enough to fit on one line, so it may never wrap, and the criterion applies only to text that wraps.
|
|
386
|
+
|
|
387
|
+
## binary-control-name-present
|
|
388
|
+
|
|
389
|
+
**Passed**
|
|
390
|
+
```html
|
|
391
|
+
<div role="switch" tabindex="0" aria-checked="false" aria-label="Bluetooth"></div>
|
|
392
|
+
```
|
|
393
|
+
`aria-label` gives the switch an accessible name.
|
|
394
|
+
|
|
395
|
+
**Failed**
|
|
396
|
+
```html
|
|
397
|
+
<div role="switch" tabindex="0" aria-checked="false"></div>
|
|
398
|
+
```
|
|
399
|
+
The switch has no name from any supported mechanism.
|
|
400
|
+
|
|
401
|
+
## button-name-present
|
|
402
|
+
|
|
403
|
+
**Passed**
|
|
404
|
+
```html
|
|
405
|
+
<button>Save</button>
|
|
406
|
+
```
|
|
407
|
+
The button's text content is its accessible name.
|
|
408
|
+
|
|
409
|
+
**Failed**
|
|
410
|
+
```html
|
|
411
|
+
<button></button>
|
|
412
|
+
```
|
|
413
|
+
The button has no text content, `aria-label`, or `aria-labelledby`.
|
|
414
|
+
|
|
415
|
+
## bypass-blocks-present
|
|
416
|
+
|
|
417
|
+
**Not applicable**
|
|
418
|
+
```html
|
|
419
|
+
<body>
|
|
420
|
+
<nav>Site nav</nav>
|
|
421
|
+
<main>Primary content</main>
|
|
422
|
+
</body>
|
|
423
|
+
```
|
|
424
|
+
A main landmark exists — a recognized bypass mechanism, so there's nothing to flag.
|
|
425
|
+
|
|
426
|
+
**Flagged (cantTell)**
|
|
427
|
+
```html
|
|
428
|
+
<body>
|
|
429
|
+
<nav>Site nav</nav>
|
|
430
|
+
<div>Primary content, no landmark, no heading, no skip link.</div>
|
|
431
|
+
</body>
|
|
432
|
+
```
|
|
433
|
+
No main landmark, working same-page anchor link, or visible heading was detected — a review prompt, not a fail, since the engine can't confirm from one snapshot whether the page truly lacks a bypass mechanism.
|
|
434
|
+
|
|
435
|
+
## canvas-text-alternative-present
|
|
436
|
+
|
|
437
|
+
**Passed**
|
|
438
|
+
```html
|
|
439
|
+
<canvas>Chart summary</canvas>
|
|
440
|
+
```
|
|
441
|
+
Fallback content between the tags provides a text alternative.
|
|
442
|
+
|
|
443
|
+
**Failed**
|
|
444
|
+
```html
|
|
445
|
+
<canvas></canvas>
|
|
446
|
+
```
|
|
447
|
+
No fallback content and no accessible name.
|
|
448
|
+
|
|
449
|
+
## canvas-text-alternative-quality
|
|
450
|
+
|
|
451
|
+
*Manual; flags every applicable element with a detected text alternative for review, not only the ones that look wrong.*
|
|
452
|
+
|
|
453
|
+
**Flagged (cantTell)**
|
|
454
|
+
```html
|
|
455
|
+
<canvas width="400" height="200">Bar chart</canvas>
|
|
456
|
+
```
|
|
457
|
+
The fallback text "Bar chart" is present but generic — worth confirming it conveys the actual data, not just the chart type.
|
|
458
|
+
|
|
459
|
+
**Not applicable**
|
|
460
|
+
```html
|
|
461
|
+
<canvas width="400" height="200"></canvas>
|
|
462
|
+
```
|
|
463
|
+
No fallback content, ARIA name, or title exists to review; that's `canvas-text-alternative-present`'s failure instead.
|
|
464
|
+
|
|
465
|
+
## combobox-name-present
|
|
466
|
+
|
|
467
|
+
**Passed**
|
|
468
|
+
```html
|
|
469
|
+
<div role="combobox" tabindex="0" aria-label="Country"></div>
|
|
470
|
+
```
|
|
471
|
+
`aria-label` names the combobox.
|
|
472
|
+
|
|
473
|
+
**Failed**
|
|
474
|
+
```html
|
|
475
|
+
<div role="combobox" tabindex="0"></div>
|
|
476
|
+
```
|
|
477
|
+
No accessible name from any supported mechanism.
|
|
478
|
+
|
|
479
|
+
## contrast-computable
|
|
480
|
+
|
|
481
|
+
*This rule is a computability gate: it never returns `fail`, only `pass` (a ratio was computed), `cantTell` (no ratio could be computed at all), or `notApplicable`.*
|
|
482
|
+
|
|
483
|
+
**Passed**
|
|
484
|
+
```html
|
|
485
|
+
<p style="background:#ffffff; color:#999999;">Body text</p>
|
|
486
|
+
```
|
|
487
|
+
Foreground and background are both flat, opaque colors, so a ratio can be computed (whether or not it later passes AA/AAA).
|
|
488
|
+
|
|
489
|
+
**Flagged (cantTell)**
|
|
490
|
+
```html
|
|
491
|
+
<p style="background-image:url(texture.png); color:#000000;">Body text</p>
|
|
492
|
+
```
|
|
493
|
+
A `background-image` behind the text is not a flat color, so no contrast ratio can be computed at all.
|
|
494
|
+
|
|
495
|
+
## contrast-enhanced
|
|
496
|
+
|
|
497
|
+
**Passed**
|
|
498
|
+
```html
|
|
499
|
+
<p style="background:#ffffff; color:#000000;">Body text</p>
|
|
500
|
+
```
|
|
501
|
+
Black on white is roughly 21:1, well above the AAA 7:1 threshold.
|
|
502
|
+
|
|
503
|
+
**Failed**
|
|
504
|
+
```html
|
|
505
|
+
<p style="background:#ffffff; color:#767676;">Body text</p>
|
|
506
|
+
```
|
|
507
|
+
Gray on white is about 4.6:1 — passes the AA minimum but fails the stricter 7:1 AAA threshold.
|
|
508
|
+
|
|
509
|
+
## contrast-minimum
|
|
510
|
+
|
|
511
|
+
**Passed**
|
|
512
|
+
```html
|
|
513
|
+
<p style="background:#ffffff; color:#767676;">Body text</p>
|
|
514
|
+
```
|
|
515
|
+
About 4.6:1, above the AA 4.5:1 minimum for normal text.
|
|
516
|
+
|
|
517
|
+
**Failed**
|
|
518
|
+
```html
|
|
519
|
+
<div style="opacity:0.5; background:#ffffff;">
|
|
520
|
+
<p style="color:#000000;">Body text</p>
|
|
521
|
+
</div>
|
|
522
|
+
```
|
|
523
|
+
50% ancestor (group) opacity over white composites to a flat gray around 4:1, below the AA minimum.
|
|
524
|
+
|
|
525
|
+
## css-focus-indicator-suppressed
|
|
526
|
+
|
|
527
|
+
**Flagged (cantTell)**
|
|
528
|
+
```html
|
|
529
|
+
<style>.reset-only:focus { outline: none; }</style>
|
|
530
|
+
<a class="reset-only" href="/pricing">Pricing</a>
|
|
531
|
+
```
|
|
532
|
+
`:focus { outline: none; }` removes the focus indicator, and no other rule matching this link draws a replacement.
|
|
533
|
+
|
|
534
|
+
**Flagged (cantTell)**
|
|
535
|
+
```html
|
|
536
|
+
<style>a { outline: none; }</style>
|
|
537
|
+
<a href="/news">News</a>
|
|
538
|
+
```
|
|
539
|
+
A rule with no `:focus` still applies while the link has focus, and it outranks the browser's own focus outline (F78). No focus rule draws a replacement.
|
|
540
|
+
|
|
541
|
+
**Not applicable**
|
|
542
|
+
```html
|
|
543
|
+
<style>.reset-shadow:focus { outline: none; box-shadow: 0 0 0 3px navy; }</style>
|
|
544
|
+
<button class="reset-shadow" type="button">Send</button>
|
|
545
|
+
```
|
|
546
|
+
The same rule that removes the outline draws a `box-shadow` in its place.
|
|
547
|
+
|
|
548
|
+
## css-hidden-focus
|
|
549
|
+
|
|
550
|
+
**Flagged (cantTell)**
|
|
551
|
+
```html
|
|
552
|
+
<a href="/x" style="opacity:0;">Hidden but tabbable link</a>
|
|
553
|
+
```
|
|
554
|
+
The link stays in the tab order despite `opacity:0` making it invisible to sighted keyboard users.
|
|
555
|
+
|
|
556
|
+
**Not applicable**
|
|
557
|
+
```html
|
|
558
|
+
<a href="/x">Visible link</a>
|
|
559
|
+
```
|
|
560
|
+
The link is both focusable and visible — nothing to flag.
|
|
561
|
+
|
|
562
|
+
**Not applicable**
|
|
563
|
+
```html
|
|
564
|
+
<style>.skip { position: absolute; left: -9999px; } .skip:focus { left: 0; }</style>
|
|
565
|
+
<a class="skip" href="#main">Skip to content</a>
|
|
566
|
+
```
|
|
567
|
+
The link is off-screen at rest, but its `:focus` rule brings it back into view when it takes focus.
|
|
568
|
+
|
|
569
|
+
## css-orientation-lock
|
|
570
|
+
|
|
571
|
+
**Passed**
|
|
572
|
+
```html
|
|
573
|
+
<html>
|
|
574
|
+
<head>
|
|
575
|
+
<title>Page title</title>
|
|
576
|
+
<style>body { background: #fff; }</style>
|
|
577
|
+
</head>
|
|
578
|
+
<body>Hi</body>
|
|
579
|
+
</html>
|
|
580
|
+
```
|
|
581
|
+
No orientation media query rotates the page.
|
|
582
|
+
|
|
583
|
+
**Failed**
|
|
584
|
+
```html
|
|
585
|
+
<html>
|
|
586
|
+
<head>
|
|
587
|
+
<title>Page title</title>
|
|
588
|
+
<style>
|
|
589
|
+
@media (orientation: landscape) {
|
|
590
|
+
body { transform: rotate(90deg); }
|
|
591
|
+
}
|
|
592
|
+
</style>
|
|
593
|
+
</head>
|
|
594
|
+
<body>Hi</body>
|
|
595
|
+
</html>
|
|
596
|
+
```
|
|
597
|
+
A landscape media query forces a 90-degree rotation, locking the page to portrait regardless of device orientation.
|
|
598
|
+
|
|
599
|
+
## definition-list-children-valid
|
|
600
|
+
|
|
601
|
+
**Passed**
|
|
602
|
+
```html
|
|
603
|
+
<dl>
|
|
604
|
+
<dt>Term</dt>
|
|
605
|
+
<dd>Definition</dd>
|
|
606
|
+
</dl>
|
|
607
|
+
```
|
|
608
|
+
`<dl>`'s only direct children are `dt`/`dd`.
|
|
609
|
+
|
|
610
|
+
**Failed**
|
|
611
|
+
```html
|
|
612
|
+
<dl>
|
|
613
|
+
<dt>Term</dt>
|
|
614
|
+
<dd>Definition</dd>
|
|
615
|
+
<p>oops</p>
|
|
616
|
+
</dl>
|
|
617
|
+
```
|
|
618
|
+
The `<p>` is a direct child of `<dl>` that isn't `dt`/`dd` (or an allowed wrapper).
|
|
619
|
+
|
|
620
|
+
## deprecated-elements-not-used
|
|
621
|
+
|
|
622
|
+
**Passed**
|
|
623
|
+
```html
|
|
624
|
+
<p>Normal content</p>
|
|
625
|
+
<blink>Sale</blink>
|
|
626
|
+
```
|
|
627
|
+
No `<marquee>` anywhere, which satisfies the rule outright; there is no separate not-applicable case. `<blink>` is not reported, since no browser makes it blink.
|
|
628
|
+
|
|
629
|
+
**Flagged (cantTell)**
|
|
630
|
+
```html
|
|
631
|
+
<marquee>Breaking news</marquee>
|
|
632
|
+
```
|
|
633
|
+
`<marquee>` scrolls its content and gives the user no way to pause, stop, or hide it; whether the page adds its own control is for a person to check. This rule never fails.
|
|
634
|
+
|
|
635
|
+
## dialog-name-present
|
|
636
|
+
|
|
637
|
+
**Passed**
|
|
638
|
+
```html
|
|
639
|
+
<div role="dialog" aria-label="Confirm deletion">
|
|
640
|
+
<p>Are you sure?</p>
|
|
641
|
+
</div>
|
|
642
|
+
```
|
|
643
|
+
`aria-label` names the dialog.
|
|
644
|
+
|
|
645
|
+
**Failed**
|
|
646
|
+
```html
|
|
647
|
+
<div role="dialog">
|
|
648
|
+
<p>Body text only.</p>
|
|
649
|
+
</div>
|
|
650
|
+
```
|
|
651
|
+
No accessible name; visible body text doesn't count toward a dialog's name.
|
|
652
|
+
|
|
653
|
+
## dlitem-parent-valid
|
|
654
|
+
|
|
655
|
+
**Passed**
|
|
656
|
+
```html
|
|
657
|
+
<dl>
|
|
658
|
+
<dt>Term</dt>
|
|
659
|
+
<dd>Definition</dd>
|
|
660
|
+
</dl>
|
|
661
|
+
```
|
|
662
|
+
Both `dt` and `dd` are inside a `<dl>` ancestor.
|
|
663
|
+
|
|
664
|
+
**Failed**
|
|
665
|
+
```html
|
|
666
|
+
<div>
|
|
667
|
+
<dt>orphan</dt>
|
|
668
|
+
</div>
|
|
669
|
+
```
|
|
670
|
+
`<dt>` has no `<dl>` ancestor at all.
|
|
671
|
+
|
|
672
|
+
## duplicate-id
|
|
673
|
+
|
|
674
|
+
*A duplicate id is a real defect under WCAG 2.0/2.1 (SC 4.1.1), but that criterion was removed in 2.2, so under this engine's default 2.2 target the same defect is coerced to `cantTell` (with a `wcagVersionScope` field) instead of `fail`. The failed example below targets `engineOptions.wcagVersion: '2.0'` to show the rule's underlying decision directly.*
|
|
675
|
+
|
|
676
|
+
**Passed**
|
|
677
|
+
```html
|
|
678
|
+
<div id="section-1">First</div>
|
|
679
|
+
<div id="section-2">Second</div>
|
|
680
|
+
```
|
|
681
|
+
Each element has a distinct id.
|
|
682
|
+
|
|
683
|
+
**Failed** (with `engineOptions.wcagVersion: '2.0'`)
|
|
684
|
+
```html
|
|
685
|
+
<div id="section-1">First</div>
|
|
686
|
+
<div id="section-1">Second</div>
|
|
687
|
+
```
|
|
688
|
+
The id `section-1` is used by two elements in the same tree, breaking `<label for>`, fragment links, and `getElementById`.
|
|
689
|
+
|
|
690
|
+
## duplicate-id-aria
|
|
691
|
+
|
|
692
|
+
*Despite being `type: 'automatic'`, this rule never returns `fail` — which element an `aria-labelledby`/`aria-describedby` reference resolves to first is well-defined, but whether that's the intended target depends on author intent, which markup doesn't carry.*
|
|
693
|
+
|
|
694
|
+
**Passed**
|
|
695
|
+
```html
|
|
696
|
+
<span id="lbl">Name</span>
|
|
697
|
+
<input type="text" aria-labelledby="lbl">
|
|
698
|
+
```
|
|
699
|
+
The id `lbl` is unique, so the reference resolves unambiguously.
|
|
700
|
+
|
|
701
|
+
**Flagged (cantTell)**
|
|
702
|
+
```html
|
|
703
|
+
<span id="lbl">Name</span>
|
|
704
|
+
<span id="lbl">Duplicate</span>
|
|
705
|
+
<input type="text" aria-labelledby="lbl">
|
|
706
|
+
```
|
|
707
|
+
`aria-labelledby="lbl"` resolves to the first element with that id — whether that's the intended target is an authoring question the markup can't answer.
|
|
708
|
+
|
|
709
|
+
## embed-text-alternative-present
|
|
710
|
+
|
|
711
|
+
**Passed**
|
|
712
|
+
```html
|
|
713
|
+
<embed src="report.pdf" type="application/pdf" aria-label="Quarterly report PDF">
|
|
714
|
+
```
|
|
715
|
+
`aria-label` gives the embed an accessible name.
|
|
716
|
+
|
|
717
|
+
**Failed**
|
|
718
|
+
```html
|
|
719
|
+
<embed src="report.pdf" type="application/pdf">
|
|
720
|
+
```
|
|
721
|
+
No accessible name and no fallback content.
|
|
722
|
+
|
|
723
|
+
## embed-text-alternative-quality
|
|
724
|
+
|
|
725
|
+
*Manual; flags every applicable element with a detected text alternative for review, not only the ones that look wrong.*
|
|
726
|
+
|
|
727
|
+
**Flagged (cantTell)**
|
|
728
|
+
```html
|
|
729
|
+
<embed src="report.pdf" type="application/pdf" title="Quarterly report PDF">
|
|
730
|
+
```
|
|
731
|
+
A `title` is present — worth confirming it identifies the document well enough for someone who can't see the embedded viewer.
|
|
732
|
+
|
|
733
|
+
**Not applicable**
|
|
734
|
+
```html
|
|
735
|
+
<embed src="report.pdf" type="application/pdf">
|
|
736
|
+
```
|
|
737
|
+
No aria-label, resolved aria-labelledby, or title exists to review; that's `embed-text-alternative-present`'s failure instead.
|
|
738
|
+
|
|
739
|
+
## empty-heading
|
|
740
|
+
|
|
741
|
+
**Flagged (cantTell)**
|
|
742
|
+
```html
|
|
743
|
+
<h2></h2>
|
|
744
|
+
```
|
|
745
|
+
The heading has no accessible name at all — nothing for a screen reader to announce.
|
|
746
|
+
|
|
747
|
+
**Not applicable**
|
|
748
|
+
```html
|
|
749
|
+
<h2>Section title</h2>
|
|
750
|
+
```
|
|
751
|
+
The heading has visible text content.
|
|
752
|
+
|
|
753
|
+
## empty-table-header
|
|
754
|
+
|
|
755
|
+
**Flagged (cantTell)**
|
|
756
|
+
```html
|
|
757
|
+
<table><tr><th></th></tr></table>
|
|
758
|
+
```
|
|
759
|
+
The header cell has no text content and no accessible name.
|
|
760
|
+
|
|
761
|
+
**Not applicable**
|
|
762
|
+
```html
|
|
763
|
+
<table><tr><th>Name</th></tr></table>
|
|
764
|
+
```
|
|
765
|
+
The header cell has visible text content.
|
|
766
|
+
|
|
767
|
+
## focus-order-semantics
|
|
768
|
+
|
|
769
|
+
**Flagged (cantTell)**
|
|
770
|
+
```html
|
|
771
|
+
<div role="heading" tabindex="0">Fake focusable heading</div>
|
|
772
|
+
```
|
|
773
|
+
A heading role has no interactive behavior, so making it tabbable is unusual and worth a look.
|
|
774
|
+
|
|
775
|
+
**Not applicable**
|
|
776
|
+
```html
|
|
777
|
+
<div role="button" tabindex="0">Real custom button</div>
|
|
778
|
+
```
|
|
779
|
+
`role="button"` is an interactive role, so being tabbable is expected.
|
|
780
|
+
|
|
781
|
+
## form-control-label-quality
|
|
782
|
+
|
|
783
|
+
*This rule is scoped to fields that already have a visible programmatic label; a field with no visible label at all belongs to `form-control-programmatic-label-present`/`form-control-programmatic-label-quality` instead.*
|
|
784
|
+
|
|
785
|
+
**Flagged (cantTell)**
|
|
786
|
+
```html
|
|
787
|
+
<label>Label <input type="text" name="f1"></label>
|
|
788
|
+
```
|
|
789
|
+
The label text is the placeholder word "Label" itself, not a real description of the field.
|
|
790
|
+
|
|
791
|
+
**Not applicable**
|
|
792
|
+
```html
|
|
793
|
+
<label for="e">Email address:</label>
|
|
794
|
+
<input id="e" type="text">
|
|
795
|
+
```
|
|
796
|
+
The label text is a genuine, descriptive name for the field.
|
|
797
|
+
|
|
798
|
+
## form-control-programmatic-label-present
|
|
799
|
+
|
|
800
|
+
**Passed**
|
|
801
|
+
```html
|
|
802
|
+
<label for="email">Full name</label>
|
|
803
|
+
<input id="email" type="text">
|
|
804
|
+
```
|
|
805
|
+
The `<label for>` association gives the input a programmatic label.
|
|
806
|
+
|
|
807
|
+
**Failed**
|
|
808
|
+
```html
|
|
809
|
+
<input type="text">
|
|
810
|
+
```
|
|
811
|
+
No `<label>`, `aria-label`, or `aria-labelledby` — nothing programmatic names the field.
|
|
812
|
+
|
|
813
|
+
## form-control-programmatic-label-quality
|
|
814
|
+
|
|
815
|
+
**Flagged (cantTell)**
|
|
816
|
+
```html
|
|
817
|
+
<input type="text" placeholder="Email address">
|
|
818
|
+
```
|
|
819
|
+
The control's only name comes from `placeholder`, which disappears once the user starts typing.
|
|
820
|
+
|
|
821
|
+
**Not applicable**
|
|
822
|
+
```html
|
|
823
|
+
<label for="email">Email address</label>
|
|
824
|
+
<input type="text" id="email" placeholder="name@example.com">
|
|
825
|
+
```
|
|
826
|
+
A persistent `<label>` provides the name; `placeholder` is just a format hint here.
|
|
827
|
+
|
|
828
|
+
## form-control-single-label
|
|
829
|
+
|
|
830
|
+
**Passed**
|
|
831
|
+
```html
|
|
832
|
+
<label>Name <input type="text"></label>
|
|
833
|
+
```
|
|
834
|
+
A single wrapping label; only one labelling mechanism applies.
|
|
835
|
+
|
|
836
|
+
**Failed**
|
|
837
|
+
```html
|
|
838
|
+
<label for="address">Address</label>
|
|
839
|
+
<label for="address">Mailing address</label>
|
|
840
|
+
<input id="address" type="text">
|
|
841
|
+
```
|
|
842
|
+
Two separate `label[for]` elements both target the same control.
|
|
843
|
+
|
|
844
|
+
## heading-order
|
|
845
|
+
|
|
846
|
+
**Flagged (cantTell)**
|
|
847
|
+
```html
|
|
848
|
+
<h1>Title</h1>
|
|
849
|
+
<h3>Subsection</h3>
|
|
850
|
+
```
|
|
851
|
+
The heading level jumps from 1 to 3, skipping level 2.
|
|
852
|
+
|
|
853
|
+
**Not applicable**
|
|
854
|
+
```html
|
|
855
|
+
<h1>Title</h1>
|
|
856
|
+
<h2>Section</h2>
|
|
857
|
+
<h3>Subsection</h3>
|
|
858
|
+
```
|
|
859
|
+
Each heading level increases by at most one at a time.
|
|
860
|
+
|
|
861
|
+
## heading-quality
|
|
862
|
+
|
|
863
|
+
*Headings with no accessible name at all belong to `empty-heading`, not this rule.*
|
|
864
|
+
|
|
865
|
+
**Flagged (cantTell)**
|
|
866
|
+
```html
|
|
867
|
+
<h3>Heading</h3>
|
|
868
|
+
<p>We are open Monday through Friday from 10 to 16.</p>
|
|
869
|
+
```
|
|
870
|
+
The heading text is the generic placeholder word "Heading" itself, not a description of the section.
|
|
871
|
+
|
|
872
|
+
**Not applicable**
|
|
873
|
+
```html
|
|
874
|
+
<h3>Opening hours</h3>
|
|
875
|
+
<p>We are open Monday through Friday from 10 to 16.</p>
|
|
876
|
+
```
|
|
877
|
+
The heading names its actual topic.
|
|
878
|
+
|
|
879
|
+
## html-lang-attr-present
|
|
880
|
+
|
|
881
|
+
**Passed**
|
|
882
|
+
```html
|
|
883
|
+
<html lang="en">
|
|
884
|
+
<head><title>Page title</title></head>
|
|
885
|
+
<body>Hi</body>
|
|
886
|
+
</html>
|
|
887
|
+
```
|
|
888
|
+
A non-empty, syntactically valid language tag is declared.
|
|
889
|
+
|
|
890
|
+
**Failed**
|
|
891
|
+
```html
|
|
892
|
+
<html>
|
|
893
|
+
<head><title>Page title</title></head>
|
|
894
|
+
<body>Hi</body>
|
|
895
|
+
</html>
|
|
896
|
+
```
|
|
897
|
+
No `lang` attribute at all on `<html>`.
|
|
898
|
+
|
|
899
|
+
## html-xml-lang-mismatch
|
|
900
|
+
|
|
901
|
+
**Passed**
|
|
902
|
+
```html
|
|
903
|
+
<html lang="en-US" xml:lang="en-GB">
|
|
904
|
+
```
|
|
905
|
+
Primary subtags agree (`en`); regional subtags are allowed to differ.
|
|
906
|
+
|
|
907
|
+
**Failed**
|
|
908
|
+
```html
|
|
909
|
+
<html lang="en" xml:lang="fr">
|
|
910
|
+
```
|
|
911
|
+
`lang` and `xml:lang` declare different primary languages.
|
|
912
|
+
|
|
913
|
+
## identical-iframes-same-purpose
|
|
914
|
+
|
|
915
|
+
*Despite being `type: 'automatic'`, this rule never returns `fail` — frames sharing a name but resolving to different URLs can still be legitimately equivalent, and nothing in the markup settles that either way.*
|
|
916
|
+
|
|
917
|
+
**Passed**
|
|
918
|
+
```html
|
|
919
|
+
<iframe title="Weather widget" src="https://example.test/weather"></iframe>
|
|
920
|
+
<iframe title="Weather widget" src="https://example.test/weather"></iframe>
|
|
921
|
+
```
|
|
922
|
+
Both frames share the name "Weather widget" and embed the same resource.
|
|
923
|
+
|
|
924
|
+
**Flagged (cantTell)**
|
|
925
|
+
```html
|
|
926
|
+
<iframe title="Weather widget" src="https://example.test/weather?city=nyc"></iframe>
|
|
927
|
+
<iframe title="Weather widget" src="https://example.test/weather?city=sf"></iframe>
|
|
928
|
+
```
|
|
929
|
+
Both frames share the same name but resolve to different resources — possibly two legitimately equivalent widgets, possibly a mislabeled frame; the markup alone doesn't settle it.
|
|
930
|
+
|
|
931
|
+
## identical-links-same-purpose
|
|
932
|
+
|
|
933
|
+
**Flagged (cantTell)**
|
|
934
|
+
```html
|
|
935
|
+
<a href="/articles/1">Read more</a>
|
|
936
|
+
<a href="/articles/2">Read more</a>
|
|
937
|
+
```
|
|
938
|
+
Both links share the accessible name "Read more" but resolve to different destinations.
|
|
939
|
+
|
|
940
|
+
**Not applicable**
|
|
941
|
+
```html
|
|
942
|
+
<a href="/articles/1">Read more: Quarterly results</a>
|
|
943
|
+
<a href="/articles/2">Read more: Product launch</a>
|
|
944
|
+
```
|
|
945
|
+
Each link's accessible name is distinct, so there's no same-name group to compare destinations within.
|
|
946
|
+
|
|
947
|
+
## iframe-focusable-content
|
|
948
|
+
|
|
949
|
+
**Passed**
|
|
950
|
+
```html
|
|
951
|
+
<iframe tabindex="-1" width="300" height="150" srcdoc="<p>No focusable content</p>"></iframe>
|
|
952
|
+
```
|
|
953
|
+
`tabindex="-1"` removes the frame from the tab order, and nothing inside it is focusable either.
|
|
954
|
+
|
|
955
|
+
**Failed**
|
|
956
|
+
```html
|
|
957
|
+
<iframe tabindex="-1" width="300" height="150" srcdoc="<a href='/x'>Link inside</a>"></iframe>
|
|
958
|
+
```
|
|
959
|
+
The host frame is skipped by `tabindex="-1"`, but the link inside it is still reachable by Tab — the browser does not propagate the negative tabindex into the embedded document.
|
|
960
|
+
|
|
961
|
+
## iframe-name-present
|
|
962
|
+
|
|
963
|
+
**Passed**
|
|
964
|
+
```html
|
|
965
|
+
<iframe title="Video player" src="content.html"></iframe>
|
|
966
|
+
```
|
|
967
|
+
The `title` attribute names the frame.
|
|
968
|
+
|
|
969
|
+
**Failed**
|
|
970
|
+
```html
|
|
971
|
+
<iframe src="content.html"></iframe>
|
|
972
|
+
```
|
|
973
|
+
No `title`, `aria-label`, or `aria-labelledby`.
|
|
974
|
+
|
|
975
|
+
## iframe-title-unique
|
|
976
|
+
|
|
977
|
+
*Deprecated since 1.8.0 in favour of [`identical-iframes-same-purpose`](#identical-iframes-same-purpose): this rule reports `notApplicable` on every page, so it has no passing or failing example of its own.*
|
|
978
|
+
|
|
979
|
+
**Not applicable**
|
|
980
|
+
```html
|
|
981
|
+
<iframe title="Video player" src="a.html"></iframe>
|
|
982
|
+
<iframe title="Video player" src="b.html"></iframe>
|
|
983
|
+
```
|
|
984
|
+
Formerly a `fail` for the shared title. WCAG 4.1.2 does not require frame names to be unique; `identical-iframes-same-purpose` now raises this pair as `cantTell`, since the two frames share a name but embed different resources.
|
|
985
|
+
|
|
986
|
+
## image-redundant-alt
|
|
987
|
+
|
|
988
|
+
**Flagged (cantTell)**
|
|
989
|
+
```html
|
|
990
|
+
<a href="/"><img alt="Home" src="home.png">Home</a>
|
|
991
|
+
```
|
|
992
|
+
The image's `alt` text ("Home") duplicates the adjacent visible link text word for word — a screen reader announces it twice.
|
|
993
|
+
|
|
994
|
+
**Not applicable**
|
|
995
|
+
```html
|
|
996
|
+
<a href="/"><img alt="Home icon" src="home.png">Home</a>
|
|
997
|
+
```
|
|
998
|
+
The `alt` text ("Home icon") differs from the adjacent text ("Home").
|
|
999
|
+
|
|
1000
|
+
## img-alt-decorative
|
|
1001
|
+
|
|
1002
|
+
*Manual; flags every applicable element with a detected text alternative for review, not only the ones that look wrong.*
|
|
1003
|
+
|
|
1004
|
+
**Flagged (cantTell)**
|
|
1005
|
+
```html
|
|
1006
|
+
<img src="quarterly-trend.png" alt="">
|
|
1007
|
+
```
|
|
1008
|
+
Marked decorative with `alt=""`, but a filename like "quarterly-trend" suggests this might actually be an informative chart, not decoration.
|
|
1009
|
+
|
|
1010
|
+
**Not applicable**
|
|
1011
|
+
```html
|
|
1012
|
+
<button aria-label="Close"><img src="x-icon.png" alt=""></button>
|
|
1013
|
+
```
|
|
1014
|
+
The button's own `aria-label` already provides the accessible name; the icon's exclusion isn't this element's decorative question to review.
|
|
1015
|
+
|
|
1016
|
+
## img-alt-present
|
|
1017
|
+
|
|
1018
|
+
**Passed**
|
|
1019
|
+
```html
|
|
1020
|
+
<img src="image.png" alt="Green rectangle">
|
|
1021
|
+
```
|
|
1022
|
+
A non-empty `alt` is present.
|
|
1023
|
+
|
|
1024
|
+
**Failed**
|
|
1025
|
+
```html
|
|
1026
|
+
<img src="image.png">
|
|
1027
|
+
```
|
|
1028
|
+
No `alt` attribute at all (an empty `alt=""` would instead pass as decorative).
|
|
1029
|
+
|
|
1030
|
+
## img-alt-quality
|
|
1031
|
+
|
|
1032
|
+
*Manual; flags every applicable element with a detected text alternative for review, not only the ones that look wrong.*
|
|
1033
|
+
|
|
1034
|
+
**Flagged (cantTell)**
|
|
1035
|
+
```html
|
|
1036
|
+
<img src="chart.png" alt="image">
|
|
1037
|
+
```
|
|
1038
|
+
Non-empty alt is present, but "image" restates the element type instead of describing what the chart shows — worth a reviewer's judgment.
|
|
1039
|
+
|
|
1040
|
+
**Not applicable**
|
|
1041
|
+
```html
|
|
1042
|
+
<img src="border.png" alt="">
|
|
1043
|
+
```
|
|
1044
|
+
Empty alt marks the image decorative; that's `img-alt-decorative`'s question, not this rule's.
|
|
1045
|
+
|
|
1046
|
+
## input-image-alt-decorative
|
|
1047
|
+
|
|
1048
|
+
*Manual; flags every applicable element with a detected text alternative for review, not only the ones that look wrong.*
|
|
1049
|
+
|
|
1050
|
+
**Flagged (cantTell)**
|
|
1051
|
+
```html
|
|
1052
|
+
<input type="image" src="submit.png" alt="" title="Submit order">
|
|
1053
|
+
```
|
|
1054
|
+
Empty alt is unusual on a functional control; a `title` still names it, but worth confirming the empty alt was deliberate.
|
|
1055
|
+
|
|
1056
|
+
**Not applicable**
|
|
1057
|
+
```html
|
|
1058
|
+
<input type="image" src="submit.png" alt="Submit order">
|
|
1059
|
+
```
|
|
1060
|
+
Non-empty alt is `input-image-alt-quality`'s question, not this rule's.
|
|
1061
|
+
|
|
1062
|
+
## input-image-alt-present
|
|
1063
|
+
|
|
1064
|
+
**Passed**
|
|
1065
|
+
```html
|
|
1066
|
+
<input type="image" src="search.png" alt="Search">
|
|
1067
|
+
```
|
|
1068
|
+
A non-empty `alt` names the image button.
|
|
1069
|
+
|
|
1070
|
+
**Failed**
|
|
1071
|
+
```html
|
|
1072
|
+
<input type="image" src="search.png">
|
|
1073
|
+
```
|
|
1074
|
+
No `alt` attribute.
|
|
1075
|
+
|
|
1076
|
+
## input-image-alt-quality
|
|
1077
|
+
|
|
1078
|
+
*Manual; flags every applicable element with a detected text alternative for review, not only the ones that look wrong.*
|
|
1079
|
+
|
|
1080
|
+
**Flagged (cantTell)**
|
|
1081
|
+
```html
|
|
1082
|
+
<input type="image" src="submit.png" alt="Submit order">
|
|
1083
|
+
```
|
|
1084
|
+
Non-empty alt is present on this image button, worth confirming "Submit order" matches what the button actually does. An image button named only by `aria-label`, `aria-labelledby` or `title` is asked about too.
|
|
1085
|
+
|
|
1086
|
+
**Not applicable**
|
|
1087
|
+
```html
|
|
1088
|
+
<input type="image" src="submit.png" alt="" title="Submit order">
|
|
1089
|
+
```
|
|
1090
|
+
Empty alt on a functional control is `input-image-alt-decorative`'s question, not this rule's.
|
|
1091
|
+
|
|
1092
|
+
## label-in-name
|
|
1093
|
+
|
|
1094
|
+
**Passed**
|
|
1095
|
+
```html
|
|
1096
|
+
<button aria-label="Save changes">Save</button>
|
|
1097
|
+
```
|
|
1098
|
+
The accessible name ("Save changes") contains the visible text ("Save").
|
|
1099
|
+
|
|
1100
|
+
**Failed**
|
|
1101
|
+
```html
|
|
1102
|
+
<button aria-label="Submit form">Save</button>
|
|
1103
|
+
```
|
|
1104
|
+
The accessible name doesn't contain the visible label text at all.
|
|
1105
|
+
|
|
1106
|
+
## label-title-only
|
|
1107
|
+
|
|
1108
|
+
**Flagged (cantTell)**
|
|
1109
|
+
```html
|
|
1110
|
+
<input title="Name">
|
|
1111
|
+
```
|
|
1112
|
+
The `title` attribute is the field's only naming mechanism — it shows as a tooltip, not a persistent visible label.
|
|
1113
|
+
|
|
1114
|
+
**Not applicable**
|
|
1115
|
+
```html
|
|
1116
|
+
<label>Name <input title="Enter your full name"></label>
|
|
1117
|
+
```
|
|
1118
|
+
A real `<label>` already names the field; `title` is just supplementary.
|
|
1119
|
+
|
|
1120
|
+
## landmark-banner-is-top-level
|
|
1121
|
+
|
|
1122
|
+
**Flagged (cantTell)**
|
|
1123
|
+
```html
|
|
1124
|
+
<div role="navigation">
|
|
1125
|
+
<div role="banner">Nested banner</div>
|
|
1126
|
+
</div>
|
|
1127
|
+
```
|
|
1128
|
+
A banner landmark nested inside another landmark no longer functions as the page's single top-level header.
|
|
1129
|
+
|
|
1130
|
+
**Not applicable**
|
|
1131
|
+
```html
|
|
1132
|
+
<header>Site header</header>
|
|
1133
|
+
```
|
|
1134
|
+
The banner is not nested inside any other landmark.
|
|
1135
|
+
|
|
1136
|
+
## landmark-complementary-is-top-level
|
|
1137
|
+
|
|
1138
|
+
**Flagged (cantTell)**
|
|
1139
|
+
```html
|
|
1140
|
+
<nav aria-label="Section navigation">
|
|
1141
|
+
<div role="complementary">Related links</div>
|
|
1142
|
+
</nav>
|
|
1143
|
+
```
|
|
1144
|
+
A complementary landmark nested inside a navigation landmark.
|
|
1145
|
+
|
|
1146
|
+
**Not applicable**
|
|
1147
|
+
```html
|
|
1148
|
+
<aside>Related links</aside>
|
|
1149
|
+
```
|
|
1150
|
+
The complementary landmark is not nested inside any other landmark.
|
|
1151
|
+
|
|
1152
|
+
## landmark-contentinfo-is-top-level
|
|
1153
|
+
|
|
1154
|
+
**Flagged (cantTell)**
|
|
1155
|
+
```html
|
|
1156
|
+
<div role="navigation">
|
|
1157
|
+
<div role="contentinfo">Nested contentinfo</div>
|
|
1158
|
+
</div>
|
|
1159
|
+
```
|
|
1160
|
+
A contentinfo landmark nested inside another landmark no longer functions as the page's single top-level footer.
|
|
1161
|
+
|
|
1162
|
+
**Not applicable**
|
|
1163
|
+
```html
|
|
1164
|
+
<footer>Site footer</footer>
|
|
1165
|
+
```
|
|
1166
|
+
The contentinfo is not nested inside any other landmark.
|
|
1167
|
+
|
|
1168
|
+
## landmark-main-is-top-level
|
|
1169
|
+
|
|
1170
|
+
**Flagged (cantTell)**
|
|
1171
|
+
```html
|
|
1172
|
+
<div role="navigation">
|
|
1173
|
+
<main>Nested main</main>
|
|
1174
|
+
</div>
|
|
1175
|
+
```
|
|
1176
|
+
The main landmark is nested inside a navigation landmark instead of being top-level.
|
|
1177
|
+
|
|
1178
|
+
**Not applicable**
|
|
1179
|
+
```html
|
|
1180
|
+
<main>Content</main>
|
|
1181
|
+
```
|
|
1182
|
+
The main landmark is not nested inside any other landmark.
|
|
1183
|
+
|
|
1184
|
+
## landmark-no-duplicate-banner
|
|
1185
|
+
|
|
1186
|
+
**Flagged (cantTell)**
|
|
1187
|
+
```html
|
|
1188
|
+
<body>
|
|
1189
|
+
<header>First</header>
|
|
1190
|
+
<header>Second</header>
|
|
1191
|
+
</body>
|
|
1192
|
+
```
|
|
1193
|
+
Two top-level `<header>` elements both resolve to the banner role.
|
|
1194
|
+
|
|
1195
|
+
**Not applicable**
|
|
1196
|
+
```html
|
|
1197
|
+
<body>
|
|
1198
|
+
<header>Site header</header>
|
|
1199
|
+
</body>
|
|
1200
|
+
```
|
|
1201
|
+
Only one banner landmark exists.
|
|
1202
|
+
|
|
1203
|
+
## landmark-no-duplicate-contentinfo
|
|
1204
|
+
|
|
1205
|
+
**Flagged (cantTell)**
|
|
1206
|
+
```html
|
|
1207
|
+
<body>
|
|
1208
|
+
<footer>First</footer>
|
|
1209
|
+
<footer>Second</footer>
|
|
1210
|
+
</body>
|
|
1211
|
+
```
|
|
1212
|
+
Two top-level `<footer>` elements both resolve to the contentinfo role.
|
|
1213
|
+
|
|
1214
|
+
**Not applicable**
|
|
1215
|
+
```html
|
|
1216
|
+
<body>
|
|
1217
|
+
<footer>Site footer</footer>
|
|
1218
|
+
</body>
|
|
1219
|
+
```
|
|
1220
|
+
Only one contentinfo landmark exists.
|
|
1221
|
+
|
|
1222
|
+
## landmark-no-duplicate-main
|
|
1223
|
+
|
|
1224
|
+
**Flagged (cantTell)**
|
|
1225
|
+
```html
|
|
1226
|
+
<body>
|
|
1227
|
+
<main>First</main>
|
|
1228
|
+
<main>Second</main>
|
|
1229
|
+
</body>
|
|
1230
|
+
```
|
|
1231
|
+
Two `<main>` elements are both exposed to assistive technology as main landmarks.
|
|
1232
|
+
|
|
1233
|
+
**Not applicable**
|
|
1234
|
+
```html
|
|
1235
|
+
<body>
|
|
1236
|
+
<main>Only one</main>
|
|
1237
|
+
</body>
|
|
1238
|
+
```
|
|
1239
|
+
Only one main landmark exists.
|
|
1240
|
+
|
|
1241
|
+
## landmark-one-main
|
|
1242
|
+
|
|
1243
|
+
**Flagged (cantTell)**
|
|
1244
|
+
```html
|
|
1245
|
+
<body>
|
|
1246
|
+
<header>Site</header>
|
|
1247
|
+
<p>Primary content, no landmark.</p>
|
|
1248
|
+
</body>
|
|
1249
|
+
```
|
|
1250
|
+
No `<main>` or `role="main"` anywhere on the page.
|
|
1251
|
+
|
|
1252
|
+
**Not applicable**
|
|
1253
|
+
```html
|
|
1254
|
+
<body>
|
|
1255
|
+
<main>Primary content</main>
|
|
1256
|
+
</body>
|
|
1257
|
+
```
|
|
1258
|
+
A `<main>` landmark exists, so there is nothing to flag.
|
|
1259
|
+
|
|
1260
|
+
## landmark-unique
|
|
1261
|
+
|
|
1262
|
+
**Flagged (cantTell)**
|
|
1263
|
+
```html
|
|
1264
|
+
<nav aria-label="Site">First</nav>
|
|
1265
|
+
<nav aria-label="Site">Second</nav>
|
|
1266
|
+
```
|
|
1267
|
+
Two navigation landmarks share the identical accessible name, so a screen reader user can't tell them apart in the landmarks list.
|
|
1268
|
+
|
|
1269
|
+
**Not applicable**
|
|
1270
|
+
```html
|
|
1271
|
+
<nav aria-label="Primary">Primary nav</nav>
|
|
1272
|
+
<nav aria-label="Footer">Footer nav</nav>
|
|
1273
|
+
```
|
|
1274
|
+
Each navigation landmark has a distinct accessible name.
|
|
1275
|
+
|
|
1276
|
+
## link-in-text-block
|
|
1277
|
+
|
|
1278
|
+
**Passed**
|
|
1279
|
+
```html
|
|
1280
|
+
<p>Read <a href="#" style="text-decoration:underline">this underlined link</a> for more information.</p>
|
|
1281
|
+
```
|
|
1282
|
+
The link is underlined, distinguishing it from surrounding text by more than color.
|
|
1283
|
+
|
|
1284
|
+
**Failed**
|
|
1285
|
+
```html
|
|
1286
|
+
<p style="color:#222222;">Read <a href="#" style="text-decoration:none; color:#2a2a2a;">this link</a> for more information.</p>
|
|
1287
|
+
```
|
|
1288
|
+
The only difference from surrounding text is a color shift with about 1.1:1 contrast, well under the 3:1 minimum, and no underline or weight/style change.
|
|
1289
|
+
|
|
1290
|
+
**Flagged (cantTell)**
|
|
1291
|
+
```html
|
|
1292
|
+
<p style="color:#000000;">Read <a href="#" style="text-decoration:none; color:#d00000;">this link</a> for more information.</p>
|
|
1293
|
+
```
|
|
1294
|
+
The link's color contrasts with the text by more than 3:1, but color is its only cue. Technique G183 also needs a non-color cue on hover and focus, which a person checks.
|
|
1295
|
+
|
|
1296
|
+
## link-name-present
|
|
1297
|
+
|
|
1298
|
+
**Passed**
|
|
1299
|
+
```html
|
|
1300
|
+
<a href="/x">Read more</a>
|
|
1301
|
+
```
|
|
1302
|
+
The link's text content is its accessible name.
|
|
1303
|
+
|
|
1304
|
+
**Failed**
|
|
1305
|
+
```html
|
|
1306
|
+
<a href="/x"></a>
|
|
1307
|
+
```
|
|
1308
|
+
No text content, `aria-label`, or `aria-labelledby`.
|
|
1309
|
+
|
|
1310
|
+
## link-name-quality
|
|
1311
|
+
|
|
1312
|
+
*A link with no accessible name at all is `link-name-present`'s concern, not this rule's.*
|
|
1313
|
+
|
|
1314
|
+
**Flagged (cantTell)**
|
|
1315
|
+
```html
|
|
1316
|
+
<a href="/x">Click here</a>
|
|
1317
|
+
```
|
|
1318
|
+
"Click here" is a known generic phrase that doesn't describe the link's destination out of context.
|
|
1319
|
+
|
|
1320
|
+
**Not applicable**
|
|
1321
|
+
```html
|
|
1322
|
+
<a href="/x">Download the 2026 pricing guide</a>
|
|
1323
|
+
```
|
|
1324
|
+
The link text is specific and descriptive.
|
|
1325
|
+
|
|
1326
|
+
## list-children-valid
|
|
1327
|
+
|
|
1328
|
+
**Passed**
|
|
1329
|
+
```html
|
|
1330
|
+
<ul>
|
|
1331
|
+
<li>Apple</li>
|
|
1332
|
+
<li>Banana</li>
|
|
1333
|
+
</ul>
|
|
1334
|
+
```
|
|
1335
|
+
`<ul>`'s only direct children are `<li>`.
|
|
1336
|
+
|
|
1337
|
+
**Failed**
|
|
1338
|
+
```html
|
|
1339
|
+
<ul>
|
|
1340
|
+
<li>Elderberry</li>
|
|
1341
|
+
<div>wrapper</div>
|
|
1342
|
+
</ul>
|
|
1343
|
+
```
|
|
1344
|
+
A `<div>` is a direct child of `<ul>` alongside the `<li>`.
|
|
1345
|
+
|
|
1346
|
+
## listbox-name-present
|
|
1347
|
+
|
|
1348
|
+
**Passed**
|
|
1349
|
+
```html
|
|
1350
|
+
<ul role="listbox" tabindex="0" aria-label="Fruit"></ul>
|
|
1351
|
+
```
|
|
1352
|
+
`aria-label` names the listbox.
|
|
1353
|
+
|
|
1354
|
+
**Failed**
|
|
1355
|
+
```html
|
|
1356
|
+
<ul role="listbox" tabindex="0"></ul>
|
|
1357
|
+
```
|
|
1358
|
+
No accessible name from any supported mechanism.
|
|
1359
|
+
|
|
1360
|
+
## listitem-parent-valid
|
|
1361
|
+
|
|
1362
|
+
**Passed**
|
|
1363
|
+
```html
|
|
1364
|
+
<ul>
|
|
1365
|
+
<li>Apple</li>
|
|
1366
|
+
</ul>
|
|
1367
|
+
```
|
|
1368
|
+
`<li>` is inside a `<ul>`.
|
|
1369
|
+
|
|
1370
|
+
**Failed**
|
|
1371
|
+
```html
|
|
1372
|
+
<div>
|
|
1373
|
+
<li>Date</li>
|
|
1374
|
+
</div>
|
|
1375
|
+
```
|
|
1376
|
+
`<li>` has no `<ul>`/`<ol>`/`role="list"` ancestor.
|
|
1377
|
+
|
|
1378
|
+
## manual-review
|
|
1379
|
+
|
|
1380
|
+
*This rule has no `notApplicable` branch at all — it returns `cantTell` unconditionally on every page, including an empty `<body>` — so there is only one example, not a pair.*
|
|
1381
|
+
|
|
1382
|
+
**Flagged (cantTell)**
|
|
1383
|
+
```html
|
|
1384
|
+
<body>
|
|
1385
|
+
<p>Any page content.</p>
|
|
1386
|
+
</body>
|
|
1387
|
+
```
|
|
1388
|
+
Keyboard operability and focus order always need a person driving the page; this rule states that need on every run.
|
|
1389
|
+
|
|
1390
|
+
## media-alternative-transcript-evidence
|
|
1391
|
+
|
|
1392
|
+
**Not applicable**
|
|
1393
|
+
```html
|
|
1394
|
+
<video src="talk.mp4" controls aria-describedby="t1"></video>
|
|
1395
|
+
<div id="t1">Transcript: Welcome to today's talk...</div>
|
|
1396
|
+
```
|
|
1397
|
+
`aria-describedby` binds to text explicitly identified as a transcript — strong evidence.
|
|
1398
|
+
|
|
1399
|
+
**Flagged (cantTell)**
|
|
1400
|
+
```html
|
|
1401
|
+
<video src="talk.mp4" controls></video>
|
|
1402
|
+
```
|
|
1403
|
+
No transcript or text-alternative evidence was found near the media element; the engine can't confirm one doesn't exist elsewhere on the page, so this is conservative, not a fail.
|
|
1404
|
+
|
|
1405
|
+
## menuitem-name-present
|
|
1406
|
+
|
|
1407
|
+
**Passed**
|
|
1408
|
+
```html
|
|
1409
|
+
<div role="menuitem" tabindex="0">Cut</div>
|
|
1410
|
+
```
|
|
1411
|
+
Visible text content is a valid name source for role="menuitem".
|
|
1412
|
+
|
|
1413
|
+
**Failed**
|
|
1414
|
+
```html
|
|
1415
|
+
<div role="menuitem" tabindex="0"></div>
|
|
1416
|
+
```
|
|
1417
|
+
No name and no content.
|
|
1418
|
+
|
|
1419
|
+
## meta-refresh-no-exceptions
|
|
1420
|
+
|
|
1421
|
+
**Passed**
|
|
1422
|
+
```html
|
|
1423
|
+
<meta http-equiv="refresh" content="0; URL=/x">
|
|
1424
|
+
```
|
|
1425
|
+
A delay of exactly 0 is an immediate redirect; there's nothing to interrupt a user mid-read.
|
|
1426
|
+
|
|
1427
|
+
**Failed**
|
|
1428
|
+
```html
|
|
1429
|
+
<meta http-equiv="refresh" content="30; URL=/x">
|
|
1430
|
+
```
|
|
1431
|
+
At AAA, any positive delay fails, with no exemption for a long delay.
|
|
1432
|
+
|
|
1433
|
+
## meta-refresh-timing-absent
|
|
1434
|
+
|
|
1435
|
+
**Passed**
|
|
1436
|
+
```html
|
|
1437
|
+
<meta http-equiv="refresh" content="90000; URL=/x">
|
|
1438
|
+
```
|
|
1439
|
+
A delay over 20 hours (72000s) is exempt under WCAG 2.2.1 Exception 3.
|
|
1440
|
+
|
|
1441
|
+
**Failed**
|
|
1442
|
+
```html
|
|
1443
|
+
<meta http-equiv="refresh" content="30; URL=/x">
|
|
1444
|
+
```
|
|
1445
|
+
A 30-second delay refreshes the page on a timer the user cannot pause, stop, or extend.
|
|
1446
|
+
|
|
1447
|
+
## meta-viewport-large
|
|
1448
|
+
|
|
1449
|
+
**Flagged (cantTell)**
|
|
1450
|
+
```html
|
|
1451
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
|
1452
|
+
```
|
|
1453
|
+
`user-scalable=no` blocks zooming past whatever scale the page loads at, short of the AAA 500% best-practice target.
|
|
1454
|
+
|
|
1455
|
+
**Not applicable**
|
|
1456
|
+
```html
|
|
1457
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5">
|
|
1458
|
+
```
|
|
1459
|
+
`maximum-scale=5` (500%) already meets the best-practice ceiling this rule checks toward.
|
|
1460
|
+
|
|
1461
|
+
## meta-viewport-zoom-enabled
|
|
1462
|
+
|
|
1463
|
+
**Passed**
|
|
1464
|
+
```html
|
|
1465
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5">
|
|
1466
|
+
```
|
|
1467
|
+
`maximum-scale` of 5 (500%) still allows zooming well past the 200% WCAG 1.4.4 requires.
|
|
1468
|
+
|
|
1469
|
+
**Failed**
|
|
1470
|
+
```html
|
|
1471
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
|
1472
|
+
```
|
|
1473
|
+
`user-scalable=no` disables pinch-zoom entirely.
|
|
1474
|
+
|
|
1475
|
+
## meter-name-present
|
|
1476
|
+
|
|
1477
|
+
**Passed**
|
|
1478
|
+
```html
|
|
1479
|
+
<div role="meter" aria-valuenow="80" aria-label="Disk usage"></div>
|
|
1480
|
+
```
|
|
1481
|
+
`aria-label` names the meter.
|
|
1482
|
+
|
|
1483
|
+
**Failed**
|
|
1484
|
+
```html
|
|
1485
|
+
<div role="meter" aria-valuenow="80"></div>
|
|
1486
|
+
```
|
|
1487
|
+
No accessible name from any supported mechanism.
|
|
1488
|
+
|
|
1489
|
+
## mouse-only-event-handlers
|
|
1490
|
+
|
|
1491
|
+
**Flagged (cantTell)**
|
|
1492
|
+
```html
|
|
1493
|
+
<div onmouseover="show()" onmouseout="hide()">Hover-only tooltip trigger</div>
|
|
1494
|
+
```
|
|
1495
|
+
`onmouseover`/`onmouseout` have no `onfocus`/`onblur` counterpart, so a keyboard user can never trigger this behavior.
|
|
1496
|
+
|
|
1497
|
+
**Flagged (cantTell)**
|
|
1498
|
+
```html
|
|
1499
|
+
<div onmouseover="show()" onfocus="show()">Menu</div>
|
|
1500
|
+
```
|
|
1501
|
+
The `onfocus` handler never runs: the `<div>` cannot take focus.
|
|
1502
|
+
|
|
1503
|
+
**Not applicable**
|
|
1504
|
+
```html
|
|
1505
|
+
<div onmouseover="show()" onmouseout="hide()" onfocus="show()" onblur="hide()" tabindex="0">Hover + focus pair</div>
|
|
1506
|
+
```
|
|
1507
|
+
Each mouse handler is paired with a keyboard-equivalent focus handler.
|
|
1508
|
+
|
|
1509
|
+
## nested-interactive-controls-absent
|
|
1510
|
+
|
|
1511
|
+
**Passed**
|
|
1512
|
+
```html
|
|
1513
|
+
<button>Click me</button>
|
|
1514
|
+
```
|
|
1515
|
+
No nested interactive control.
|
|
1516
|
+
|
|
1517
|
+
**Failed**
|
|
1518
|
+
```html
|
|
1519
|
+
<button>
|
|
1520
|
+
<input type="checkbox"> Accept
|
|
1521
|
+
</button>
|
|
1522
|
+
```
|
|
1523
|
+
A `<button>` contains a nested `<input type="checkbox">`, another focusable control.
|
|
1524
|
+
|
|
1525
|
+
## no-autoplay-audio
|
|
1526
|
+
|
|
1527
|
+
**Flagged (cantTell)**
|
|
1528
|
+
```html
|
|
1529
|
+
<audio autoplay src="x.mp3"></audio>
|
|
1530
|
+
```
|
|
1531
|
+
The audio autoplays unmuted with no `controls` attribute, giving the user no way to pause or stop it.
|
|
1532
|
+
|
|
1533
|
+
**Not applicable**
|
|
1534
|
+
```html
|
|
1535
|
+
<audio autoplay controls src="x.mp3"></audio>
|
|
1536
|
+
```
|
|
1537
|
+
Native `controls` gives the user a pause/volume mechanism.
|
|
1538
|
+
|
|
1539
|
+
**Flagged (cantTell)**
|
|
1540
|
+
```html
|
|
1541
|
+
<embed src="welcome.mp3">
|
|
1542
|
+
```
|
|
1543
|
+
An `<embed>` or `<object>` that loads sound or video, and any `<bgsound>`, may play as soon as the page loads, and it has no `controls` or `muted` to read. One with `autostart="false"` (or a `<param>` saying so) is not asked about.
|
|
1544
|
+
|
|
1545
|
+
## object-text-alternative-present
|
|
1546
|
+
|
|
1547
|
+
**Passed**
|
|
1548
|
+
```html
|
|
1549
|
+
<object data="chart.svg" type="image/svg+xml">Sales chart for 2024</object>
|
|
1550
|
+
```
|
|
1551
|
+
Fallback content between the tags counts as a text alternative.
|
|
1552
|
+
|
|
1553
|
+
**Failed**
|
|
1554
|
+
```html
|
|
1555
|
+
<object data="chart.svg" type="image/svg+xml"></object>
|
|
1556
|
+
```
|
|
1557
|
+
No fallback content and no accessible name.
|
|
1558
|
+
|
|
1559
|
+
## object-text-alternative-quality
|
|
1560
|
+
|
|
1561
|
+
*Manual; flags every applicable element with a detected text alternative for review, not only the ones that look wrong.*
|
|
1562
|
+
|
|
1563
|
+
**Flagged (cantTell)**
|
|
1564
|
+
```html
|
|
1565
|
+
<object data="brochure.pdf" type="application/pdf">Product brochure (PDF)</object>
|
|
1566
|
+
```
|
|
1567
|
+
Fallback text is present — worth confirming "Product brochure (PDF)" is genuinely equivalent to the embedded document, not just a filler caption.
|
|
1568
|
+
|
|
1569
|
+
**Not applicable**
|
|
1570
|
+
```html
|
|
1571
|
+
<object data="brochure.pdf" type="application/pdf"></object>
|
|
1572
|
+
```
|
|
1573
|
+
No fallback content, ARIA name, or title exists to review; that's `object-text-alternative-present`'s failure instead.
|
|
1574
|
+
|
|
1575
|
+
## option-name-present
|
|
1576
|
+
|
|
1577
|
+
**Passed**
|
|
1578
|
+
```html
|
|
1579
|
+
<div role="option" tabindex="0">Apple</div>
|
|
1580
|
+
```
|
|
1581
|
+
Visible text content is a valid name source for role="option".
|
|
1582
|
+
|
|
1583
|
+
**Failed**
|
|
1584
|
+
```html
|
|
1585
|
+
<div role="option" tabindex="0"></div>
|
|
1586
|
+
```
|
|
1587
|
+
No name and no content.
|
|
1588
|
+
|
|
1589
|
+
## p-as-heading
|
|
1590
|
+
|
|
1591
|
+
**Flagged (cantTell)**
|
|
1592
|
+
```html
|
|
1593
|
+
<style>.fakeHeading { font-weight: bold; font-size: 22px; }</style>
|
|
1594
|
+
<p class="fakeHeading">Section Title</p>
|
|
1595
|
+
```
|
|
1596
|
+
Bold, heading-sized, and short — visually a heading, but marked up as a plain `<p>` with no heading semantics.
|
|
1597
|
+
|
|
1598
|
+
**Not applicable**
|
|
1599
|
+
```html
|
|
1600
|
+
<p>Just a normal paragraph of body text that is not bold at all.</p>
|
|
1601
|
+
```
|
|
1602
|
+
Normal weight and size; nothing visually suggests a heading.
|
|
1603
|
+
|
|
1604
|
+
**Flagged (cantTell)**
|
|
1605
|
+
```html
|
|
1606
|
+
<div style="font-size: 24px"><span style="font-weight: bold">Our team</span></div>
|
|
1607
|
+
```
|
|
1608
|
+
A `<div>` holding only text is checked like a `<p>`, and the bold can come from a styled `<span>` inside it. A `<div>` that holds other blocks, and text inside a button, label or table header, is not asked about.
|
|
1609
|
+
|
|
1610
|
+
## page-has-heading-one
|
|
1611
|
+
|
|
1612
|
+
**Flagged (cantTell)**
|
|
1613
|
+
```html
|
|
1614
|
+
<body>
|
|
1615
|
+
<h2>Section</h2>
|
|
1616
|
+
<p>No top-level heading anywhere on the page.</p>
|
|
1617
|
+
</body>
|
|
1618
|
+
```
|
|
1619
|
+
No `<h1>` or `role="heading" aria-level="1"` exists.
|
|
1620
|
+
|
|
1621
|
+
**Not applicable**
|
|
1622
|
+
```html
|
|
1623
|
+
<body>
|
|
1624
|
+
<h1>Page title</h1>
|
|
1625
|
+
</body>
|
|
1626
|
+
```
|
|
1627
|
+
A level-one heading exists, so there is nothing to flag.
|
|
1628
|
+
|
|
1629
|
+
## page-title-patterns
|
|
1630
|
+
|
|
1631
|
+
**Flagged (cantTell)**
|
|
1632
|
+
```html
|
|
1633
|
+
<title>Home</title>
|
|
1634
|
+
```
|
|
1635
|
+
"Home" is one of the conservative generic-title signals this rule reviews.
|
|
1636
|
+
|
|
1637
|
+
**Not applicable**
|
|
1638
|
+
```html
|
|
1639
|
+
<title>Quarterly Sales Report — Q3 2026</title>
|
|
1640
|
+
```
|
|
1641
|
+
The title is specific to the page's content, matching none of the low-descriptiveness patterns.
|
|
1642
|
+
|
|
1643
|
+
## page-title-present
|
|
1644
|
+
|
|
1645
|
+
**Passed**
|
|
1646
|
+
```html
|
|
1647
|
+
<html>
|
|
1648
|
+
<head><title>Quarterly report</title></head>
|
|
1649
|
+
<body>Hi</body>
|
|
1650
|
+
</html>
|
|
1651
|
+
```
|
|
1652
|
+
The document has a non-empty `<title>`.
|
|
1653
|
+
|
|
1654
|
+
**Failed**
|
|
1655
|
+
```html
|
|
1656
|
+
<html>
|
|
1657
|
+
<head></head>
|
|
1658
|
+
<body>Hi</body>
|
|
1659
|
+
</html>
|
|
1660
|
+
```
|
|
1661
|
+
No `<title>` element anywhere in the document.
|
|
1662
|
+
|
|
1663
|
+
## password-paste-enabled
|
|
1664
|
+
|
|
1665
|
+
*This rule never fails — every case is either raised for review or left alone. An `addEventListener`-attached paste handler leaves no trace in markup, so it can't be demonstrated in a static example.*
|
|
1666
|
+
|
|
1667
|
+
**Flagged (cantTell)**
|
|
1668
|
+
```html
|
|
1669
|
+
<label for="p">Password</label>
|
|
1670
|
+
<input type="password" id="p" onpaste="return false">
|
|
1671
|
+
```
|
|
1672
|
+
The inline handler cancels paste outright, blocking a password manager or a copied one-time code.
|
|
1673
|
+
|
|
1674
|
+
**Not applicable**
|
|
1675
|
+
```html
|
|
1676
|
+
<label for="p">Password</label>
|
|
1677
|
+
<input type="password" id="p" autocomplete="current-password">
|
|
1678
|
+
```
|
|
1679
|
+
No paste handler is attached at all.
|
|
1680
|
+
|
|
1681
|
+
## presentation-role-conflict
|
|
1682
|
+
|
|
1683
|
+
**Flagged (cantTell)**
|
|
1684
|
+
```html
|
|
1685
|
+
<div role="presentation" aria-label="Conflicting">Content</div>
|
|
1686
|
+
```
|
|
1687
|
+
`role="presentation"` asks assistive technology to skip this element, but `aria-label` supplies a name — per WAI-ARIA conflict resolution, the global attribute wins and the presentation role is dropped, so the element (and its name) come back into the tree unexpectedly.
|
|
1688
|
+
|
|
1689
|
+
**Not applicable**
|
|
1690
|
+
```html
|
|
1691
|
+
<div role="presentation">Decorative</div>
|
|
1692
|
+
```
|
|
1693
|
+
No conflicting naming attribute is present alongside the presentation role.
|
|
1694
|
+
|
|
1695
|
+
## presentational-children-focusable-absent
|
|
1696
|
+
|
|
1697
|
+
**Passed**
|
|
1698
|
+
```html
|
|
1699
|
+
<button>Save</button>
|
|
1700
|
+
```
|
|
1701
|
+
No presentational content that also happens to be focusable.
|
|
1702
|
+
|
|
1703
|
+
**Failed**
|
|
1704
|
+
```html
|
|
1705
|
+
<button>
|
|
1706
|
+
Save
|
|
1707
|
+
<span role="button" aria-label="save options" aria-expanded="false" tabindex="0">▼</span>
|
|
1708
|
+
</button>
|
|
1709
|
+
```
|
|
1710
|
+
The nested `<span>` carries its own interactive role and tab stop inside the button's presentational content.
|
|
1711
|
+
|
|
1712
|
+
## progressbar-name-present
|
|
1713
|
+
|
|
1714
|
+
**Passed**
|
|
1715
|
+
```html
|
|
1716
|
+
<div role="progressbar" aria-valuenow="50" aria-label="Upload progress"></div>
|
|
1717
|
+
```
|
|
1718
|
+
`aria-label` names the progress bar.
|
|
1719
|
+
|
|
1720
|
+
**Failed**
|
|
1721
|
+
```html
|
|
1722
|
+
<div role="progressbar" aria-valuenow="50"></div>
|
|
1723
|
+
```
|
|
1724
|
+
No accessible name from any supported mechanism.
|
|
1725
|
+
|
|
1726
|
+
## region
|
|
1727
|
+
|
|
1728
|
+
**Flagged (cantTell)**
|
|
1729
|
+
```html
|
|
1730
|
+
<body>
|
|
1731
|
+
<p>Stray paragraph, not inside any landmark.</p>
|
|
1732
|
+
</body>
|
|
1733
|
+
```
|
|
1734
|
+
This paragraph sits directly under `<body>`, outside every landmark.
|
|
1735
|
+
|
|
1736
|
+
**Not applicable**
|
|
1737
|
+
```html
|
|
1738
|
+
<body>
|
|
1739
|
+
<main>
|
|
1740
|
+
<p>Everything lives inside a landmark.</p>
|
|
1741
|
+
</main>
|
|
1742
|
+
</body>
|
|
1743
|
+
```
|
|
1744
|
+
All page content is contained within a landmark region.
|
|
1745
|
+
|
|
1746
|
+
## role-img-text-alternative-present
|
|
1747
|
+
|
|
1748
|
+
**Passed**
|
|
1749
|
+
```html
|
|
1750
|
+
<span role="img" aria-label="Notifications"></span>
|
|
1751
|
+
```
|
|
1752
|
+
`aria-label` provides the text alternative.
|
|
1753
|
+
|
|
1754
|
+
**Failed**
|
|
1755
|
+
```html
|
|
1756
|
+
<span role="img">notifications</span>
|
|
1757
|
+
```
|
|
1758
|
+
Text content is not a valid name source for an element with an explicit role="img".
|
|
1759
|
+
|
|
1760
|
+
## scope-attr-valid
|
|
1761
|
+
|
|
1762
|
+
**Flagged (cantTell)**
|
|
1763
|
+
```html
|
|
1764
|
+
<table><tr><th scope="column">Name</th></tr></table>
|
|
1765
|
+
```
|
|
1766
|
+
"column" is not a valid `scope` value (the correct token is "col").
|
|
1767
|
+
|
|
1768
|
+
**Not applicable**
|
|
1769
|
+
```html
|
|
1770
|
+
<table><tr><th scope="col">Name</th></tr></table>
|
|
1771
|
+
```
|
|
1772
|
+
"col" is a valid `scope` value.
|
|
1773
|
+
|
|
1774
|
+
## scrollable-region-focusable
|
|
1775
|
+
|
|
1776
|
+
**Flagged (cantTell)**
|
|
1777
|
+
```html
|
|
1778
|
+
<style>.scrollY { overflow-y: auto; height: 100px; }</style>
|
|
1779
|
+
<div class="scrollY">Plain long text content with no interactive children at all.</div>
|
|
1780
|
+
```
|
|
1781
|
+
The region can scroll, but it has no `tabindex` of its own and contains no focusable descendant, so a keyboard user can never reach it to scroll it.
|
|
1782
|
+
|
|
1783
|
+
**Not applicable**
|
|
1784
|
+
```html
|
|
1785
|
+
<style>.scrollY { overflow-y: auto; height: 100px; }</style>
|
|
1786
|
+
<div class="scrollY" tabindex="0">Long content, but the region itself is focusable.</div>
|
|
1787
|
+
```
|
|
1788
|
+
`tabindex="0"` makes the scrollable region itself reachable by keyboard.
|
|
1789
|
+
|
|
1790
|
+
## searchbox-name-present
|
|
1791
|
+
|
|
1792
|
+
**Passed**
|
|
1793
|
+
```html
|
|
1794
|
+
<div role="searchbox" tabindex="0" aria-label="Search the site"></div>
|
|
1795
|
+
```
|
|
1796
|
+
`aria-label` names the searchbox.
|
|
1797
|
+
|
|
1798
|
+
**Failed**
|
|
1799
|
+
```html
|
|
1800
|
+
<div role="searchbox" tabindex="0"></div>
|
|
1801
|
+
```
|
|
1802
|
+
No accessible name from any supported mechanism.
|
|
1803
|
+
|
|
1804
|
+
## server-side-image-map-absent
|
|
1805
|
+
|
|
1806
|
+
**Passed**
|
|
1807
|
+
```html
|
|
1808
|
+
<img src="image.png" usemap="#nav" alt="Site map">
|
|
1809
|
+
<map name="nav">
|
|
1810
|
+
<area href="/x" shape="rect" coords="0,0,10,10" alt="Go">
|
|
1811
|
+
</map>
|
|
1812
|
+
```
|
|
1813
|
+
A client-side image map has no `ismap` attribute, which satisfies the rule outright. (An `<img ismap>` outside a link does nothing and makes the rule not applicable.)
|
|
1814
|
+
|
|
1815
|
+
**Flagged (cantTell)**
|
|
1816
|
+
```html
|
|
1817
|
+
<a href="/map-handler">
|
|
1818
|
+
<img src="image.png" ismap alt="Site map">
|
|
1819
|
+
</a>
|
|
1820
|
+
```
|
|
1821
|
+
`ismap` depends on the browser sending click coordinates to the server, so the map's regions cannot be reached from the keyboard; whether the page offers the same destinations as links is for a person to check. This rule never fails.
|
|
1822
|
+
|
|
1823
|
+
## skip-link
|
|
1824
|
+
|
|
1825
|
+
**Flagged (cantTell)**
|
|
1826
|
+
```html
|
|
1827
|
+
<a href="#missing">Skip to main content</a>
|
|
1828
|
+
```
|
|
1829
|
+
The link's fragment target doesn't exist anywhere on the page, so activating it goes nowhere.
|
|
1830
|
+
|
|
1831
|
+
**Flagged (cantTell)**
|
|
1832
|
+
```html
|
|
1833
|
+
<a href="#contenu">Aller au contenu</a>
|
|
1834
|
+
```
|
|
1835
|
+
French skip-link wording is recognised as well, and the target is missing.
|
|
1836
|
+
|
|
1837
|
+
**Not applicable**
|
|
1838
|
+
```html
|
|
1839
|
+
<a href="#target">Skip to content</a>
|
|
1840
|
+
<div id="target">Target content</div>
|
|
1841
|
+
```
|
|
1842
|
+
The fragment target exists and is usable.
|
|
1843
|
+
|
|
1844
|
+
## slider-name-present
|
|
1845
|
+
|
|
1846
|
+
**Passed**
|
|
1847
|
+
```html
|
|
1848
|
+
<input type="range" aria-label="Volume">
|
|
1849
|
+
```
|
|
1850
|
+
`aria-label` names the native range input acting as a slider.
|
|
1851
|
+
|
|
1852
|
+
**Failed**
|
|
1853
|
+
```html
|
|
1854
|
+
<div role="slider" tabindex="0" aria-valuenow="5" aria-valuemin="0" aria-valuemax="10"></div>
|
|
1855
|
+
```
|
|
1856
|
+
No accessible name from any supported mechanism.
|
|
1857
|
+
|
|
1858
|
+
## spinbutton-name-present
|
|
1859
|
+
|
|
1860
|
+
**Passed**
|
|
1861
|
+
```html
|
|
1862
|
+
<div role="spinbutton" tabindex="0" aria-label="Quantity"></div>
|
|
1863
|
+
```
|
|
1864
|
+
`aria-label` names the spinbutton.
|
|
1865
|
+
|
|
1866
|
+
**Failed**
|
|
1867
|
+
```html
|
|
1868
|
+
<div role="spinbutton" tabindex="0"></div>
|
|
1869
|
+
```
|
|
1870
|
+
No accessible name from any supported mechanism.
|
|
1871
|
+
|
|
1872
|
+
## summary-name-present
|
|
1873
|
+
|
|
1874
|
+
**Passed**
|
|
1875
|
+
```html
|
|
1876
|
+
<details>
|
|
1877
|
+
<summary>More details</summary>
|
|
1878
|
+
<p>Body</p>
|
|
1879
|
+
</details>
|
|
1880
|
+
```
|
|
1881
|
+
Visible text content names the `<summary>`.
|
|
1882
|
+
|
|
1883
|
+
**Failed**
|
|
1884
|
+
```html
|
|
1885
|
+
<details>
|
|
1886
|
+
<summary></summary>
|
|
1887
|
+
<p>Body</p>
|
|
1888
|
+
</details>
|
|
1889
|
+
```
|
|
1890
|
+
No text content and no accessible name.
|
|
1891
|
+
|
|
1892
|
+
## svg-image-text-alternative-present
|
|
1893
|
+
|
|
1894
|
+
**Passed**
|
|
1895
|
+
```html
|
|
1896
|
+
<svg width="40" height="24">
|
|
1897
|
+
<image href="logo.png"><title>Logo</title></image>
|
|
1898
|
+
</svg>
|
|
1899
|
+
```
|
|
1900
|
+
A `<title>` inside the SVG `<image>` provides the alternative.
|
|
1901
|
+
|
|
1902
|
+
**Failed**
|
|
1903
|
+
```html
|
|
1904
|
+
<svg width="40" height="24" aria-label="container">
|
|
1905
|
+
<image href="logo.png"></image>
|
|
1906
|
+
</svg>
|
|
1907
|
+
```
|
|
1908
|
+
The `<image>` itself has no `<title>`/`<desc>`/ARIA name (the outer `<svg>`'s label doesn't transfer to it).
|
|
1909
|
+
|
|
1910
|
+
## svg-text-alternative-present
|
|
1911
|
+
|
|
1912
|
+
**Passed**
|
|
1913
|
+
```html
|
|
1914
|
+
<svg role="img"><title>Close</title></svg>
|
|
1915
|
+
```
|
|
1916
|
+
`<title>` provides the accessible name for an SVG with an explicit img role.
|
|
1917
|
+
|
|
1918
|
+
**Failed**
|
|
1919
|
+
```html
|
|
1920
|
+
<svg role="img"></svg>
|
|
1921
|
+
```
|
|
1922
|
+
No `<title>`, `<desc>`, or ARIA name.
|
|
1923
|
+
|
|
1924
|
+
## svg-text-alternative-quality
|
|
1925
|
+
|
|
1926
|
+
*Manual; flags every applicable element with a detected text alternative for review, not only the ones that look wrong.*
|
|
1927
|
+
|
|
1928
|
+
**Flagged (cantTell)**
|
|
1929
|
+
```html
|
|
1930
|
+
<svg role="img" viewBox="0 0 24 24"><title>Growth chart</title><path d="M0 0h24v24H0z"/></svg>
|
|
1931
|
+
```
|
|
1932
|
+
A `<title>` is present — worth confirming "Growth chart" describes what the chart actually shows, not just that it's a chart.
|
|
1933
|
+
|
|
1934
|
+
**Not applicable**
|
|
1935
|
+
```html
|
|
1936
|
+
<svg role="img" viewBox="0 0 24 24"><path d="M0 0h24v24H0z"/></svg>
|
|
1937
|
+
```
|
|
1938
|
+
No `<title>`/`<desc>` text, aria-label, or resolved aria-labelledby exists to review; that's `svg-text-alternative-present`'s failure instead.
|
|
1939
|
+
|
|
1940
|
+
## tab-name-present
|
|
1941
|
+
|
|
1942
|
+
**Passed**
|
|
1943
|
+
```html
|
|
1944
|
+
<div role="tab" tabindex="0">Apple</div>
|
|
1945
|
+
```
|
|
1946
|
+
Visible text content names the tab.
|
|
1947
|
+
|
|
1948
|
+
**Failed**
|
|
1949
|
+
```html
|
|
1950
|
+
<div role="tab" tabindex="0"></div>
|
|
1951
|
+
```
|
|
1952
|
+
No name and no content.
|
|
1953
|
+
|
|
1954
|
+
## tabindex
|
|
1955
|
+
|
|
1956
|
+
**Flagged (cantTell)**
|
|
1957
|
+
```html
|
|
1958
|
+
<div tabindex="3">Reordered</div>
|
|
1959
|
+
```
|
|
1960
|
+
A positive `tabindex` pulls this element out of the document's natural tab order and ahead of everything else, which usually surprises keyboard users.
|
|
1961
|
+
|
|
1962
|
+
**Not applicable**
|
|
1963
|
+
```html
|
|
1964
|
+
<div tabindex="0">Focusable</div>
|
|
1965
|
+
```
|
|
1966
|
+
`tabindex="0"` places the element in the natural tab order without reordering anything.
|
|
1967
|
+
|
|
1968
|
+
## table-duplicate-name
|
|
1969
|
+
|
|
1970
|
+
**Flagged (cantTell)**
|
|
1971
|
+
```html
|
|
1972
|
+
<table summary="Sales data">
|
|
1973
|
+
<caption>Sales data</caption>
|
|
1974
|
+
<tr><td>1</td></tr>
|
|
1975
|
+
</table>
|
|
1976
|
+
```
|
|
1977
|
+
The `summary` attribute repeats the caption text word for word instead of adding anything new.
|
|
1978
|
+
|
|
1979
|
+
**Not applicable**
|
|
1980
|
+
```html
|
|
1981
|
+
<table summary="Data collected in fiscal year 2025">
|
|
1982
|
+
<caption>Sales data</caption>
|
|
1983
|
+
<tr><td>1</td></tr>
|
|
1984
|
+
</table>
|
|
1985
|
+
```
|
|
1986
|
+
`summary` provides information the caption doesn't.
|
|
1987
|
+
|
|
1988
|
+
## table-fake-caption
|
|
1989
|
+
|
|
1990
|
+
**Flagged (cantTell)**
|
|
1991
|
+
```html
|
|
1992
|
+
<table>
|
|
1993
|
+
<tr><td>Monthly Sales Report</td></tr>
|
|
1994
|
+
<tr><td>Jan</td><td>100</td></tr>
|
|
1995
|
+
<tr><td>Feb</td><td>200</td></tr>
|
|
1996
|
+
</table>
|
|
1997
|
+
```
|
|
1998
|
+
A lone cell sits alone in the first row above rows that all have multiple cells, and there's no real `<caption>` — it looks like a caption pretending to be a data cell.
|
|
1999
|
+
|
|
2000
|
+
**Not applicable**
|
|
2001
|
+
```html
|
|
2002
|
+
<table>
|
|
2003
|
+
<caption>Monthly Sales Report</caption>
|
|
2004
|
+
<tr><td>Title cell</td></tr>
|
|
2005
|
+
<tr><td>Jan</td><td>100</td></tr>
|
|
2006
|
+
</table>
|
|
2007
|
+
```
|
|
2008
|
+
The table has a real `<caption>` element.
|
|
2009
|
+
|
|
2010
|
+
## table-headers-attr-valid
|
|
2011
|
+
|
|
2012
|
+
**Passed**
|
|
2013
|
+
```html
|
|
2014
|
+
<table>
|
|
2015
|
+
<tr><th id="h1">Name</th></tr>
|
|
2016
|
+
<tr><td headers="h1">Apple</td></tr>
|
|
2017
|
+
</table>
|
|
2018
|
+
```
|
|
2019
|
+
`headers="h1"` resolves to a real cell in the same table.
|
|
2020
|
+
|
|
2021
|
+
**Failed**
|
|
2022
|
+
```html
|
|
2023
|
+
<table>
|
|
2024
|
+
<tr><th id="h2">Name</th></tr>
|
|
2025
|
+
<tr><td headers="missing">Banana</td></tr>
|
|
2026
|
+
</table>
|
|
2027
|
+
```
|
|
2028
|
+
`headers` references an id that doesn't exist in the table.
|
|
2029
|
+
|
|
2030
|
+
## table-th-has-data-cells
|
|
2031
|
+
|
|
2032
|
+
**Passed**
|
|
2033
|
+
```html
|
|
2034
|
+
<table>
|
|
2035
|
+
<tr><th>Name</th></tr>
|
|
2036
|
+
<tr><td>Apple</td></tr>
|
|
2037
|
+
</table>
|
|
2038
|
+
```
|
|
2039
|
+
The table has at least one `<td>` for the `<th>` to head.
|
|
2040
|
+
|
|
2041
|
+
**Failed**
|
|
2042
|
+
```html
|
|
2043
|
+
<table>
|
|
2044
|
+
<tr><th>Name</th></tr>
|
|
2045
|
+
</table>
|
|
2046
|
+
```
|
|
2047
|
+
The table has a `<th>` but zero `<td>` anywhere.
|
|
2048
|
+
|
|
2049
|
+
## target-size-minimum
|
|
2050
|
+
|
|
2051
|
+
*Confirmed against the engine's own geometry-testing harness (`patchTargetSizeEnv`), which encodes element size/position as a `data-rect` attribute rather than real CSS layout (JSDOM doesn't render). The CSS below is what a real browser would need to reproduce the same result; it isn't lifted from the fixture's own markup.*
|
|
2052
|
+
|
|
2053
|
+
**Passed**
|
|
2054
|
+
```html
|
|
2055
|
+
<button style="width:24px; height:24px;">+</button>
|
|
2056
|
+
```
|
|
2057
|
+
Meets the 24×24 CSS pixel minimum on its own.
|
|
2058
|
+
|
|
2059
|
+
**Failed**
|
|
2060
|
+
```html
|
|
2061
|
+
<button style="width:10px; height:10px;">A</button>
|
|
2062
|
+
<button style="width:10px; height:10px; margin-left:5px;">B</button>
|
|
2063
|
+
```
|
|
2064
|
+
Both targets are under 24×24 and closer together than the spacing exception allows.
|
|
2065
|
+
|
|
2066
|
+
## td-has-header
|
|
2067
|
+
|
|
2068
|
+
**Passed**
|
|
2069
|
+
```html
|
|
2070
|
+
<table>
|
|
2071
|
+
<tr><th></th><th>Q1</th><th>Q2</th><th>Q3</th></tr>
|
|
2072
|
+
<tr><th>North</th><td>1</td><td>2</td><td>3</td></tr>
|
|
2073
|
+
<tr><th>South</th><td>4</td><td>5</td><td>6</td></tr>
|
|
2074
|
+
<tr><th>East</th><td>7</td><td>8</td><td>9</td></tr>
|
|
2075
|
+
</table>
|
|
2076
|
+
```
|
|
2077
|
+
Every `<td>` has both an implicit row header (`<th>` earlier in its row) and column header (`<th>` earlier in its column). The 4x4 minimum size isn't optional — this rule is inapplicable to smaller tables.
|
|
2078
|
+
|
|
2079
|
+
**Failed**
|
|
2080
|
+
```html
|
|
2081
|
+
<table>
|
|
2082
|
+
<tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
|
|
2083
|
+
<tr><td>e</td><td>f</td><td>g</td><td>h</td></tr>
|
|
2084
|
+
<tr><td>i</td><td>j</td><td>k</td><td>l</td></tr>
|
|
2085
|
+
<tr><td>m</td><td>n</td><td>o</td><td>p</td></tr>
|
|
2086
|
+
</table>
|
|
2087
|
+
```
|
|
2088
|
+
A 4x4 table with no `<th>` anywhere — no cell has a row, column, or `headers`-attribute association.
|
|
2089
|
+
|
|
2090
|
+
## text-spacing-content-loss
|
|
2091
|
+
|
|
2092
|
+
**Passed (in a browser)**
|
|
2093
|
+
```html
|
|
2094
|
+
<p>Opening hours today, and the rest of the week from nine to five.</p>
|
|
2095
|
+
```
|
|
2096
|
+
The paragraph has no fixed size, so the text grows with the spacing and nothing is lost.
|
|
2097
|
+
|
|
2098
|
+
**Failed (in a browser)**
|
|
2099
|
+
```html
|
|
2100
|
+
<style>
|
|
2101
|
+
.hours { width: 19ch; height: 20px; overflow: hidden; }
|
|
2102
|
+
</style>
|
|
2103
|
+
<div class="hours">Opening hours today</div>
|
|
2104
|
+
```
|
|
2105
|
+
With line height 1.5, letter spacing 0.12em and word spacing 0.16em, the text wraps to a second line that the box cuts off. In jsdom, which has no layout, the rule reads only the style sheets and asks about spacing forced with `!important`.
|
|
2106
|
+
|
|
2107
|
+
## textbox-name-present
|
|
2108
|
+
|
|
2109
|
+
**Passed**
|
|
2110
|
+
```html
|
|
2111
|
+
<div role="textbox" tabindex="0" aria-label="Search the site"></div>
|
|
2112
|
+
```
|
|
2113
|
+
`aria-label` names the textbox.
|
|
2114
|
+
|
|
2115
|
+
**Failed**
|
|
2116
|
+
```html
|
|
2117
|
+
<div role="textbox" tabindex="0"></div>
|
|
2118
|
+
```
|
|
2119
|
+
No accessible name from any supported mechanism.
|
|
2120
|
+
|
|
2121
|
+
## tooltip-name-present
|
|
2122
|
+
|
|
2123
|
+
**Passed**
|
|
2124
|
+
```html
|
|
2125
|
+
<div role="tooltip">Click to save your changes</div>
|
|
2126
|
+
```
|
|
2127
|
+
Visible text content names the tooltip.
|
|
2128
|
+
|
|
2129
|
+
**Failed**
|
|
2130
|
+
```html
|
|
2131
|
+
<div role="tooltip"></div>
|
|
2132
|
+
```
|
|
2133
|
+
No text content and no accessible name.
|
|
2134
|
+
|
|
2135
|
+
## treeitem-name-present
|
|
2136
|
+
|
|
2137
|
+
**Passed**
|
|
2138
|
+
```html
|
|
2139
|
+
<div role="treeitem" tabindex="0">Apple</div>
|
|
2140
|
+
```
|
|
2141
|
+
Visible text content names the tree item.
|
|
2142
|
+
|
|
2143
|
+
**Failed**
|
|
2144
|
+
```html
|
|
2145
|
+
<div role="treeitem" tabindex="0"></div>
|
|
2146
|
+
```
|
|
2147
|
+
No name and no content.
|
|
2148
|
+
|
|
2149
|
+
## valid-lang
|
|
2150
|
+
|
|
2151
|
+
**Passed**
|
|
2152
|
+
```html
|
|
2153
|
+
<p lang="fr">Bonjour</p>
|
|
2154
|
+
```
|
|
2155
|
+
"fr" is a syntactically valid BCP 47 language tag.
|
|
2156
|
+
|
|
2157
|
+
**Failed**
|
|
2158
|
+
```html
|
|
2159
|
+
<p lang="xyz123!!">???</p>
|
|
2160
|
+
```
|
|
2161
|
+
The value isn't valid BCP 47 syntax.
|
|
2162
|
+
|
|
2163
|
+
## video-caption
|
|
2164
|
+
|
|
2165
|
+
**Flagged (cantTell)**
|
|
2166
|
+
```html
|
|
2167
|
+
<video src="x.mp4"></video>
|
|
2168
|
+
```
|
|
2169
|
+
No `<track>` element at all, so no evidence of captions. A video whose only track is `kind="subtitles"` is asked about too, since subtitles may translate the dialogue without the sound information captions carry.
|
|
2170
|
+
|
|
2171
|
+
**Not applicable**
|
|
2172
|
+
```html
|
|
2173
|
+
<video src="x.mp4"><track kind="captions" src="cap.vtt"></video>
|
|
2174
|
+
```
|
|
2175
|
+
A `kind="captions"` track with a real `src` is present.
|
|
2176
|
+
|
|
2177
|
+
## video-poster-text-alternative-present
|
|
2178
|
+
|
|
2179
|
+
**Passed**
|
|
2180
|
+
```html
|
|
2181
|
+
<video poster="poster.png" aria-label="Product demo"></video>
|
|
2182
|
+
```
|
|
2183
|
+
`aria-label` names the video element that carries the poster image.
|
|
2184
|
+
|
|
2185
|
+
**Failed**
|
|
2186
|
+
```html
|
|
2187
|
+
<video poster="poster.png"></video>
|
|
2188
|
+
```
|
|
2189
|
+
No accessible name and no fallback; a poster attribute alone isn't a text alternative.
|