@surea11y/core 1.3.0 → 1.4.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.
Files changed (166) hide show
  1. package/CHANGELOG.md +87 -2
  2. package/README.md +109 -35
  3. package/bin/surea11y-core.js +20 -0
  4. package/docs/API_STABILITY.md +26 -0
  5. package/docs/ARIA_DEPRECATION.md +95 -0
  6. package/docs/CI_INTEGRATIONS.md +7 -7
  7. package/docs/ENGINE_OPTIONS.md +1 -1
  8. package/docs/I18N.md +12 -9
  9. package/docs/INTEGRATION.md +1 -1
  10. package/docs/LIMITATIONS.md +1 -1
  11. package/docs/REPORT.md +1 -1
  12. package/docs/RULE_CATALOG.md +6 -6
  13. package/package.json +52 -16
  14. package/src/baseline.js +0 -0
  15. package/src/checks/automatic/area-alt-present.js +4 -6
  16. package/src/checks/automatic/aria-allowed-attr.js +663 -134
  17. package/src/checks/automatic/aria-allowed-role.js +2 -0
  18. package/src/checks/automatic/aria-braille-equivalent.js +2 -0
  19. package/src/checks/automatic/aria-conditional-attr.js +8 -7
  20. package/src/checks/automatic/aria-deprecated-role.js +107 -38
  21. package/src/checks/automatic/aria-hidden-body.js +6 -4
  22. package/src/checks/automatic/aria-hidden-focus.js +12 -13
  23. package/src/checks/automatic/aria-prohibited-attr.js +98 -105
  24. package/src/checks/automatic/aria-prohibited-children.js +56 -87
  25. package/src/checks/automatic/aria-required-attr.js +6 -7
  26. package/src/checks/automatic/aria-required-children.js +7 -10
  27. package/src/checks/automatic/aria-required-parent.js +20 -25
  28. package/src/checks/automatic/aria-role-name-present.js +2 -0
  29. package/src/checks/automatic/aria-roles-valid.js +33 -6
  30. package/src/checks/automatic/aria-valid-attr-value.js +18 -15
  31. package/src/checks/automatic/aria-valid-attr.js +2 -0
  32. package/src/checks/automatic/autocomplete-valid.js +39 -1
  33. package/src/checks/automatic/avoid-inline-spacing.js +181 -20
  34. package/src/checks/automatic/binary-control-name-present.js +13 -3
  35. package/src/checks/automatic/button-name-present.js +54 -21
  36. package/src/checks/automatic/canvas-text-alternative-present.js +17 -8
  37. package/src/checks/automatic/combobox-name-present.js +10 -1
  38. package/src/checks/automatic/contrast-computable.js +2 -0
  39. package/src/checks/automatic/contrast-enhanced.js +2 -0
  40. package/src/checks/automatic/contrast-minimum.js +2 -0
  41. package/src/checks/automatic/css-orientation-lock.js +21 -27
  42. package/src/checks/automatic/definition-list-children-valid.js +6 -6
  43. package/src/checks/automatic/deprecated-elements-not-used.js +4 -2
  44. package/src/checks/automatic/dialog-name-present.js +18 -11
  45. package/src/checks/automatic/dlitem-parent-valid.js +2 -0
  46. package/src/checks/automatic/duplicate-id-aria.js +4 -3
  47. package/src/checks/automatic/embed-text-alternative-present.js +2 -0
  48. package/src/checks/automatic/form-control-programmatic-label-present.js +50 -4
  49. package/src/checks/automatic/form-control-single-label.js +110 -43
  50. package/src/checks/automatic/html-xml-lang-mismatch.js +2 -0
  51. package/src/checks/automatic/iframe-focusable-content.js +246 -18
  52. package/src/checks/automatic/iframe-name-present.js +2 -0
  53. package/src/checks/automatic/iframe-title-unique.js +3 -1
  54. package/src/checks/automatic/img-alt-present.js +23 -18
  55. package/src/checks/automatic/input-image-alt-present.js +101 -52
  56. package/src/checks/automatic/label-in-name.js +97 -27
  57. package/src/checks/automatic/language-page-present.js +7 -1
  58. package/src/checks/automatic/link-in-text-block.js +2 -0
  59. package/src/checks/automatic/link-name-present.js +52 -17
  60. package/src/checks/automatic/list-children-valid.js +14 -24
  61. package/src/checks/automatic/listbox-name-present.js +10 -1
  62. package/src/checks/automatic/listitem-parent-valid.js +30 -7
  63. package/src/checks/automatic/menuitem-name-present.js +10 -1
  64. package/src/checks/automatic/meta-refresh-no-exceptions.js +33 -4
  65. package/src/checks/automatic/meta-refresh-timing-absent.js +32 -4
  66. package/src/checks/automatic/meta-viewport-zoom-enabled.js +39 -15
  67. package/src/checks/automatic/meter-name-present.js +12 -4
  68. package/src/checks/automatic/nested-interactive-controls-absent.js +177 -25
  69. package/src/checks/automatic/object-text-alternative-present.js +16 -7
  70. package/src/checks/automatic/option-name-present.js +10 -1
  71. package/src/checks/automatic/page-title-present.js +2 -0
  72. package/src/checks/automatic/progressbar-name-present.js +16 -11
  73. package/src/checks/automatic/role-img-alt-present.js +4 -4
  74. package/src/checks/automatic/searchbox-name-present.js +10 -1
  75. package/src/checks/automatic/server-side-image-map-absent.js +4 -3
  76. package/src/checks/automatic/slider-name-present.js +13 -2
  77. package/src/checks/automatic/spinbutton-name-present.js +10 -1
  78. package/src/checks/automatic/summary-name-present.js +10 -1
  79. package/src/checks/automatic/svg-image-text-alternative-present.js +2 -0
  80. package/src/checks/automatic/svg-text-alternative-present.js +17 -5
  81. package/src/checks/automatic/tab-name-present.js +10 -1
  82. package/src/checks/automatic/table-headers-attr-valid.js +3 -2
  83. package/src/checks/automatic/table-th-has-data-cells.js +69 -6
  84. package/src/checks/automatic/target-size-minimum.js +5 -0
  85. package/src/checks/automatic/td-has-header.js +24 -1
  86. package/src/checks/automatic/textbox-name-present.js +10 -1
  87. package/src/checks/automatic/tooltip-name-present.js +10 -1
  88. package/src/checks/automatic/treeitem-name-present.js +10 -1
  89. package/src/checks/automatic/valid-lang.js +18 -3
  90. package/src/checks/automatic/video-poster-text-alternative-present.js +2 -0
  91. package/src/checks/manual/accesskeys-manual.js +3 -1
  92. package/src/checks/manual/area-alt-decorative-manual.js +2 -0
  93. package/src/checks/manual/area-alt-quality-manual.js +2 -0
  94. package/src/checks/manual/aria-checked-state-mismatch-manual.js +14 -23
  95. package/src/checks/manual/aria-text-manual.js +6 -5
  96. package/src/checks/manual/bypass-blocks-present-manual.js +279 -0
  97. package/src/checks/manual/canvas-text-alternative-quality-manual.js +2 -0
  98. package/src/checks/manual/css-hidden-focus.js +184 -9
  99. package/src/checks/manual/embed-text-alternative-quality-manual.js +18 -13
  100. package/src/checks/manual/empty-heading-manual.js +17 -17
  101. package/src/checks/manual/empty-table-header-manual.js +52 -25
  102. package/src/checks/manual/focus-order-semantics-manual.js +16 -4
  103. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +2 -0
  104. package/src/checks/manual/heading-order-manual.js +28 -1
  105. package/src/checks/manual/identical-links-same-purpose-manual.js +2 -0
  106. package/src/checks/manual/image-redundant-alt-manual.js +21 -1
  107. package/src/checks/manual/img-alt-decorative-manual.js +2 -0
  108. package/src/checks/manual/img-alt-quality-manual.js +2 -0
  109. package/src/checks/manual/input-image-alt-decorative-manual.js +26 -0
  110. package/src/checks/manual/input-image-alt-quality-manual.js +2 -0
  111. package/src/checks/manual/label-title-only-manual.js +29 -22
  112. package/src/checks/manual/landmark-banner-is-top-level-manual.js +51 -55
  113. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +44 -31
  114. package/src/checks/manual/landmark-main-is-top-level-manual.js +41 -24
  115. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +16 -23
  116. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +14 -21
  117. package/src/checks/manual/landmark-no-duplicate-main-manual.js +10 -13
  118. package/src/checks/manual/landmark-one-main-manual.js +12 -23
  119. package/src/checks/manual/landmark-unique-manual.js +37 -52
  120. package/src/checks/manual/link-name-quality-manual.js +2 -0
  121. package/src/checks/manual/media-transcript-present-manual.js +2 -0
  122. package/src/checks/manual/meta-viewport-large-manual.js +3 -1
  123. package/src/checks/manual/mouse-only-event-handlers-manual.js +2 -0
  124. package/src/checks/manual/no-autoplay-audio-manual.js +2 -0
  125. package/src/checks/manual/object-text-alternative-quality-manual.js +2 -0
  126. package/src/checks/manual/p-as-heading-manual.js +2 -0
  127. package/src/checks/manual/page-has-heading-one-manual.js +12 -11
  128. package/src/checks/manual/page-title-patterns-manual.js +2 -0
  129. package/src/checks/manual/presentation-role-conflict-manual.js +51 -37
  130. package/src/checks/manual/region-manual.js +27 -36
  131. package/src/checks/manual/scope-attr-valid-manual.js +3 -1
  132. package/src/checks/manual/scrollable-region-focusable-manual.js +2 -0
  133. package/src/checks/manual/skip-link-manual.js +7 -6
  134. package/src/checks/manual/svg-text-alternative-quality-manual.js +2 -0
  135. package/src/checks/manual/tabindex-manual.js +3 -1
  136. package/src/checks/manual/table-duplicate-name-manual.js +5 -4
  137. package/src/checks/manual/table-fake-caption-manual.js +24 -3
  138. package/src/checks/manual/video-caption-manual.js +2 -0
  139. package/src/checks/manual-review.js +2 -0
  140. package/src/core.js +11820 -3317
  141. package/src/index.js +2 -0
  142. package/src/report.js +51 -9
  143. package/src/sarif.js +20 -5
  144. package/surea11y.browser.js +4943 -1388
  145. package/bin/core.js +0 -473
  146. package/docs/CLI.md +0 -128
  147. package/src/catalogs/composites.wcag.js +0 -454
  148. package/src/checks/automatic/bypass-blocks-present.js +0 -215
  149. package/src/checks/rules-and-tags.full.csv +0 -19
  150. package/src/checks/rules-and-tags.full.json +0 -259
  151. package/src/core/aria-helpers.js +0 -1211
  152. package/src/core/contrast-helpers.js +0 -1302
  153. package/src/core/dom-helpers.js +0 -4493
  154. package/src/core/dom-runner.js +0 -787
  155. package/src/core/frame-messaging.js +0 -261
  156. package/src/core/frame-scan.js +0 -190
  157. package/src/core/rollup-composites.js +0 -127
  158. package/src/core/rule-meta.js +0 -176
  159. package/src/coverage/wcag-facets.js +0 -1079
  160. package/src/coverage/wcag-version-map.js +0 -84
  161. package/src/i18n/en.js +0 -1228
  162. package/src/i18n/fr.js +0 -1185
  163. package/src/policy/contracts.js +0 -18
  164. package/src/policy/resolvePolicy.js +0 -59
  165. package/src/policy/schemas/engine-options.schema.json +0 -103
  166. package/src/policy/schemas/policy-contract.schema.json +0 -40
package/src/i18n/en.js DELETED
@@ -1,1228 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports = {
4
- img_altPresent_title: '<img> must have an alt attribute',
5
- img_altPresent_description:
6
- 'Checks that <img> elements provide an alt attribute to support a text alternative mechanism.',
7
-
8
- img_altPresent_summary_fail: 'Missing alt attribute on <img>.',
9
- img_altPresent_hint_fail: 'Add an alt attribute (use alt="" only for decorative images).',
10
- area_altPresent_title: '<area> must have an alt attribute',
11
- area_altPresent_description:
12
- 'Checks that <area> elements provide an alt attribute to support a text alternative mechanism.',
13
- area_altPresent_summary_fail: 'Missing alt attribute on <area>.',
14
- area_altPresent_hint_fail: 'Add an alt attribute (use alt="" only for decorative areas).',
15
- inputImage_altPresent_title: '<input type="image"> must have an alt attribute',
16
- inputImage_altPresent_description:
17
- 'Checks that <input type="image"> elements provide an alt attribute to support a text alternative mechanism.',
18
- inputImage_altPresent_summary_fail: 'Missing alt attribute on <input type="image">.',
19
- inputImage_altPresent_hint_fail:
20
- 'Add an alt attribute (use alt="" only when a separate accessible name is provided).',
21
- ariaHidden_programmaticFocus_review_title: 'Review aria-hidden programmatic focus',
22
- ariaHidden_programmaticFocus_review_description:
23
- 'Flags elements that are aria-hidden but considered eligible due to programmatic focus (e.g., tabindex < 0). Verify intended focus management and assistive technology exposure.',
24
- ariaHidden_programmaticFocus_review_summary:
25
- 'Review: aria-hidden element is programmatically focusable.',
26
- ariaHidden_programmaticFocus_review_hint:
27
- 'Check that focus management is intentional and that the element should remain hidden from assistive technologies.',
28
- canvas_textAltPresent_title: '<canvas> must provide a text alternative',
29
- canvas_textAltPresent_description:
30
- 'Checks that <canvas> elements provide a text alternative via fallback content or an accessible name.',
31
- canvas_textAltPresent_summary_fail: 'Missing text alternative for <canvas>.',
32
- canvas_textAltPresent_hint_fail:
33
- 'Provide fallback text inside <canvas> or an accessible name (e.g., aria-label/aria-labelledby).',
34
- svg_textAltPresent_title: '<svg> must provide a text alternative',
35
- svg_textAltPresent_description:
36
- 'Checks that inline <svg> elements provide a text alternative via a <title> element or an ARIA name (a <desc> element alone does not count).',
37
- svg_textAltPresent_summary_fail: 'Missing text alternative for <svg>.',
38
- svg_textAltPresent_hint_fail:
39
- 'Provide a <title> element with text, or an ARIA name (aria-label/aria-labelledby) — a <desc> element alone does not provide an accessible name.',
40
- object_textAltPresent_title: '<object> must provide a text alternative',
41
- object_textAltPresent_description:
42
- 'Checks that <object> elements provide a text alternative via fallback content or an accessible name.',
43
- object_textAltPresent_summary_fail: 'Missing text alternative for <object>.',
44
- object_textAltPresent_hint_fail:
45
- 'Provide meaningful fallback content inside <object>, add an accessible name (aria-label/aria-labelledby), or use a title attribute as a best-effort fallback.',
46
- embed_textAltPresent_title: '<embed> must provide a text alternative',
47
- embed_textAltPresent_description:
48
- 'Checks that <embed> elements provide a text alternative via an accessible name.',
49
- embed_textAltPresent_summary_fail: 'Missing text alternative for <embed>.',
50
- embed_textAltPresent_hint_fail:
51
- 'Add an accessible name to <embed> (aria-label/aria-labelledby preferred, or a title attribute as a best-effort fallback).',
52
- img_altQuality_title: '<img> alt text must be appropriate (manual review)',
53
- img_altQuality_description:
54
- 'Flags <img> elements with non-empty alt text for human review of appropriateness.',
55
- img_altQuality_summary_cantTell: 'Review alt text on <img> for accuracy and appropriateness.',
56
- img_altQuality_hint_cantTell:
57
- 'Ensure the alt text conveys the image’s purpose/information in context (not redundant, not filename-like).',
58
- img_altDecorative_title: '<img> with alt="" must be decorative (manual review)',
59
- img_altDecorative_description:
60
- 'Flags <img> elements with empty alt for human review that they are purely decorative.',
61
- img_altDecorative_summary_cantTell: 'Review whether <img> is decorative (alt="").',
62
- img_altDecorative_hint_cantTell:
63
- 'Confirm the image is purely decorative. If it conveys information or function, provide meaningful alt text.',
64
- area_altQuality_title: '<area> alt text must be appropriate (manual review)',
65
- area_altQuality_description:
66
- 'Flags <area> elements with non-empty alt text for human review of appropriateness.',
67
- area_altQuality_summary_cantTell: 'Review alt text on <area> for accuracy and appropriateness.',
68
- area_altQuality_hint_cantTell:
69
- 'Ensure the alt text identifies the destination/action of the image map area in context.',
70
- area_altDecorative_title: '<area> with alt="" must be decorative (manual review)',
71
- area_altDecorative_description:
72
- 'Flags <area> elements with empty alt for human review that they are decorative/non-informative.',
73
- area_altDecorative_summary_cantTell: 'Review whether <area> is decorative (alt="").',
74
- area_altDecorative_hint_cantTell:
75
- 'Confirm the area does not convey information or function. If it is interactive or meaningful, provide meaningful alt text.',
76
- inputImage_altQuality_title: '<input type="image"> alt text must be appropriate (manual review)',
77
- inputImage_altQuality_description:
78
- 'Flags <input type="image"> elements with non-empty alt text for human review of appropriateness.',
79
- inputImage_altQuality_summary_cantTell:
80
- 'Review alt text on <input type="image"> for accuracy and appropriateness.',
81
- inputImage_altQuality_hint_cantTell:
82
- 'Ensure the alt text describes the control’s action (e.g., “Search”, “Submit order”) in context.',
83
- inputImage_altDecorative_title:
84
- '<input type="image"> with alt="" must be appropriate (manual review)',
85
- inputImage_altDecorative_description:
86
- 'Flags <input type="image"> elements with empty alt for human review (usually not appropriate for functional controls).',
87
- inputImage_altDecorative_summary_cantTell: 'Review <input type="image"> with alt="".',
88
- inputImage_altDecorative_hint_cantTell:
89
- 'This control is typically functional. Confirm it has an equivalent accessible name elsewhere, or provide meaningful alt text.',
90
- canvas_textAltQuality_title: '<canvas> text alternative must be appropriate (manual review)',
91
- canvas_textAltQuality_description:
92
- 'Flags <canvas> elements with a detected text alternative for human review of equivalence and appropriateness.',
93
- canvas_textAltQuality_summary_cantTell:
94
- 'Review text alternative for <canvas> for equivalence and appropriateness.',
95
- canvas_textAltQuality_hint_cantTell:
96
- 'Confirm the fallback text or accessible name conveys the same information/function as the canvas content.',
97
- svg_textAltQuality_title: '<svg> text alternative must be appropriate (manual review)',
98
- svg_textAltQuality_description:
99
- 'Flags applicable <svg> graphics with a detected text alternative for human review of appropriateness.',
100
- svg_textAltQuality_summary_cantTell:
101
- 'Review text alternative for <svg> for accuracy and appropriateness.',
102
- svg_textAltQuality_hint_cantTell:
103
- 'Confirm the <title>/<desc> or ARIA name conveys the meaning/purpose of the graphic in context.',
104
- object_textAltQuality_title: '<object> text alternative must be appropriate (manual review)',
105
- object_textAltQuality_description:
106
- 'Flags <object> elements with detected fallback or name for human review of equivalence and appropriateness.',
107
- object_textAltQuality_summary_cantTell:
108
- 'Review text alternative for <object> for equivalence and appropriateness.',
109
- object_textAltQuality_hint_cantTell:
110
- 'Confirm the fallback content or ARIA name provides an equivalent alternative for the embedded content.',
111
- embed_textAltQuality_title: '<embed> text alternative must be appropriate (manual review)',
112
- embed_textAltQuality_description:
113
- 'Flags <embed> elements with a detected name for human review of appropriateness.',
114
- embed_textAltQuality_summary_cantTell:
115
- 'Review text alternative for <embed> for accuracy and appropriateness.',
116
- embed_textAltQuality_hint_cantTell:
117
- 'Confirm the ARIA name or title accurately identifies the embedded content in context.',
118
- videoPoster_textAltPresent_title: '<video> poster must have a text alternative',
119
- videoPoster_textAltPresent_description:
120
- 'Checks that <video> elements with a poster image provide a text alternative (accessible name).',
121
- videoPoster_textAltPresent_summary_fail: 'Missing text alternative for <video> poster.',
122
- videoPoster_textAltPresent_hint_fail:
123
- 'Provide an accessible name for the poster image (aria-label/aria-labelledby preferred, or a title attribute as a fallback).',
124
- svgImage_textAltPresent_title: 'SVG <image> must have a text alternative',
125
- svgImage_textAltPresent_description:
126
- 'Checks that SVG <image> elements provide a text alternative via <title>/<desc> or an ARIA accessible name.',
127
- svgImage_textAltPresent_summary_fail: 'Missing text alternative on SVG <image>.',
128
- svgImage_textAltPresent_hint_fail:
129
- 'Add a <title> (and optionally <desc>) inside <image>, or provide aria-label/aria-labelledby.',
130
- formControl_programmaticLabelPresent_title: 'Form controls must have a programmatic label',
131
- formControl_programmaticLabelPresent_description:
132
- 'Checks that form controls have a programmatic label via <label>, aria-label, or aria-labelledby.',
133
- formControl_programmaticLabelPresent_summary_fail:
134
- 'Form control is missing a programmatic label.',
135
- formControl_programmaticLabelPresent_hint_fail:
136
- 'Provide a <label> association, aria-label, or aria-labelledby (placeholder/title do not count as labels).',
137
-
138
- formControlAccessibleName_description:
139
- 'Fails when an applicable form control has no accessible name (e.g., label, aria-label, aria-labelledby).',
140
- formControlAccessibleName_hint_fail:
141
- 'Provide an accessible name via a <label>, aria-label, or aria-labelledby.',
142
- formControlAccessibleName_summary_fail: 'Form control has no accessible name.',
143
- formControlAccessibleName_title: 'Form controls must have an accessible name',
144
- linksTargetBlankNoopener_description:
145
- 'Ensures links with target="_blank" mitigate reverse tabnabbing risks.',
146
- linksTargetBlankNoopener_hint_cantTell: 'See guidance for this rule.',
147
- linksTargetBlankNoopener_summary_cantTell:
148
- 'Links that open in a new tab should use rel="noopener"',
149
- linksTargetBlankNoopener_title: 'Links that open in a new tab should use rel="noopener"',
150
- manualReview_description:
151
- 'Flags that a manual review of keyboard navigation and focus order is required.',
152
- manualReview_hint_cantTell: 'See guidance for this rule.',
153
- manualReview_summary_cantTell: 'Manual review: keyboard navigation and focus order',
154
- manualReview_title: 'Manual review: keyboard navigation and focus order',
155
- 'rules.img-alt-suspicious.meta.title': 'Suspicious alt text requires verification',
156
-
157
- 'rules.img-alt-suspicious.meta.description':
158
- 'Identifies images whose alt text matches common suspicious patterns (such as filenames, URLs, placeholders, or generic terms) and requires manual verification.',
159
-
160
- 'rules.img-alt-suspicious.occurrence.cantTell.summary':
161
- 'Image alt text appears suspicious ("{{alt}}" looks like {{pattern}}) and requires verification.',
162
-
163
- 'rules.img-alt-suspicious.occurrence.cantTell.hint':
164
- 'Review the alt text. Avoid filenames, URLs, placeholders, or generic terms, and ensure the text alternative describes the image’s purpose or function in context.',
165
- formControl_programmaticLabelQuality_title:
166
- 'Form controls should not rely on placeholder or title as the primary label',
167
- formControl_programmaticLabelQuality_description:
168
- 'Flags form controls whose computed accessible name relies on placeholder or title as the primary labeling method. Prefer <label> or aria-labelledby.',
169
- formControl_programmaticLabelQuality_summary_cantTell:
170
- 'Form control’s primary label is derived from {{methodLabel}}.',
171
- formControl_programmaticLabelQuality_hint_cantTell:
172
- 'Prefer a persistent <label> or aria-labelledby. Avoid relying on placeholder/title as the primary label.',
173
-
174
- html_lang_attr_title: 'Page language is declared',
175
- html_lang_attr_description:
176
- 'Checks that the default language of the page is programmatically declared.',
177
-
178
- html_lang_attr_missing_absent: 'The default language of the page is not declared.',
179
- html_lang_attr_hint_missing_absent:
180
- 'Add a lang attribute to the <html> element (for example: <html lang="en">).',
181
-
182
- html_lang_attr_missing_empty: 'The default language of the page is declared but empty.',
183
- html_lang_attr_hint_missing_empty:
184
- 'Set a valid language value in the lang attribute of the <html> element (for example: <html lang="en">).',
185
-
186
- html_lang_attr_invalid:
187
- 'The default language of the page is declared, but the value "{{lang}}" is not a valid language tag.',
188
- html_lang_attr_hint_invalid:
189
- 'Use a valid BCP 47 language tag in <html lang="…"> (for example: "en", "fr", "en-US").',
190
- mediaTranscriptPresent_title: 'Time-based media: transcript or text alternative evidence',
191
-
192
- mediaTranscriptPresent_description:
193
- 'Finds audio and video elements where a transcript or other text alternative is not strongly evidenced in the page content. This rule is conservative and reports cantTell when evidence is missing or cannot be verified.',
194
-
195
- mediaTranscriptPresent_summary_cantTell_missing:
196
- 'La présence d’une transcription ou d’une autre alternative textuelle pour cet élément {{element}} n’est pas clairement démontrée sur la page.',
197
-
198
- mediaTranscriptPresent_hint_cantTell_missing:
199
- 'Provide a clearly identified transcript or other text alternative for prerecorded audio-only or video-only media, for example a visible “Transcript” section or link.',
200
-
201
- mediaTranscriptPresent_summary_cantTell_unverified:
202
- 'A transcript or other text alternative may be available for this time-based media, but it could not be verified from the page content.',
203
-
204
- mediaTranscriptPresent_hint_cantTell_unverified:
205
- 'Ensure a clearly identified transcript or other text alternative is available and visibly or programmatically associated with the media on the page.',
206
- pageTitlePresent_title: 'Page has a non-empty title',
207
- pageTitlePresent_description:
208
- 'Checks that the page includes a non-empty <title> element that identifies the page.',
209
-
210
- pageTitlePresent_summary_fail: 'The page does not have a non-empty title.',
211
- pageTitlePresent_hint_fail:
212
- 'Add a <title> element with text that describes the page topic or purpose.',
213
- pageTitlePatterns_title: 'Page title patterns that may be insufficiently descriptive',
214
- pageTitlePatterns_description:
215
- 'Identifies page title patterns that may indicate low descriptiveness, such as generic, duplicated, or overly templated titles. This rule provides review signals and does not fail automatically.',
216
-
217
- pageTitlePatterns_summary_cantTell:
218
- 'The page title may not be descriptive enough to identify the page topic or purpose.',
219
-
220
- pageTitlePresent_summary_fail_missing: 'The page is missing a <title> element.',
221
- pageTitlePresent_summary_fail_empty: 'The page has an empty <title>.',
222
-
223
- pageTitlePatterns_summary_cantTell_duplicateAcrossPages:
224
- 'Several pages share the same title, which may make it harder to distinguish pages ({{duplicateGroups}} duplicate groups across {{pagesAnalyzed}} pages). Example: “{{exampleTitle}}”.',
225
-
226
- pageTitlePatterns_summary_cantTell_templatedAcrossPages:
227
- 'Many page titles appear highly templated, which may reduce how well titles distinguish pages ({{pagesAnalyzed}} pages).',
228
-
229
- pageTitlePatterns_summary_cantTell_generic:
230
- 'The page title is generic and may not identify the page topic or purpose.',
231
-
232
- pageTitlePatterns_summary_cantTell_veryShort:
233
- 'The page title is very short and may not identify the page topic or purpose.',
234
-
235
- pageTitlePatterns_summary_cantTell_templateLike:
236
- 'The page title appears templated and may not identify the page topic or purpose.',
237
-
238
- pageTitlePatterns_hint_cantTell:
239
- 'Review the page title and ensure it clearly identifies the page topic or purpose and helps distinguish the page from others.',
240
-
241
- // --- DOM Contrast: computability gatekeeper
242
- contrastComputable_title: 'Color contrast is computable for rendered text',
243
- contrastComputable_description:
244
- 'Determines whether sufficient information is available to compute WCAG color contrast for visible text (e.g., no gradients/images/blend modes that make background indeterminate).',
245
-
246
- contrastComputable_pass_allComputable:
247
- 'Contrast is computable for all eligible text ({{eligibleTextCount}} text node(s)).',
248
-
249
- contrastComputable_cantTell_generic: 'Contrast may not be computable ({{reasonCode}}).',
250
-
251
- contrastComputable_cantTell_bgImageOrGradient:
252
- 'Contrast is not computable because the background uses an image or gradient ({{blockerProperty}}={{blockerValue}}).',
253
-
254
- contrastComputable_cantTell_bgImage:
255
- 'Contrast is not computable because the background uses an image ({{blockerProperty}}={{blockerValue}}).',
256
-
257
- contrastComputable_cantTell_bgGradient:
258
- 'Contrast is not computable because the background uses a gradient ({{blockerProperty}}={{blockerValue}}).',
259
-
260
- contrastComputable_cantTell_bgImageAndGradient:
261
- 'Contrast is not computable because the background uses an image and a gradient ({{blockerProperty}}={{blockerValue}}).',
262
-
263
- contrastComputable_cantTell_mixBlendMode:
264
- 'Contrast is not computable because mix-blend-mode is used ({{blockerProperty}}={{blockerValue}}).',
265
-
266
- contrastComputable_cantTell_filter:
267
- 'Contrast is not computable because filter/backdrop-filter is used ({{blockerProperty}}={{blockerValue}}).',
268
-
269
- contrastComputable_cantTell_rootNotOpaque:
270
- 'Contrast is not computable because the effective background is not fully opaque at the root (alpha={{backgroundAlpha}}).',
271
-
272
- contrastComputable_cantTell_foregroundUnparsable:
273
- 'Contrast is not computable because the computed foreground color could not be parsed.',
274
-
275
- contrastComputable_cantTell_engineFailure:
276
- 'Contrast computability could not be determined due to an internal engine error ({{reasonCode}}).',
277
-
278
- contrastComputable_cantTell_backdropFilter:
279
- 'Contrast is not computable because backdrop-filter is used ({{blockerProperty}}={{blockerValue}}).',
280
-
281
- contrastComputable_cantTell_filterOrBackdropFilter:
282
- 'Contrast is not computable because filter is used ({{blockerProperty}}={{blockerValue}}).',
283
-
284
- // --- DOM Contrast: AA minimum (1.4.3)
285
- contrastMinimum_title: 'Text meets minimum color contrast (AA)',
286
- contrastMinimum_description:
287
- 'Checks that visible text has a contrast ratio of at least 4.5:1 (normal) or 3.0:1 (large), when contrast is computable from CSS.',
288
-
289
- contrastMinimum_fail_belowThreshold:
290
- 'Element has insufficient color contrast of {{ratio}}:1 (foreground: {{foregroundHex}}, background: {{backgroundHex}}, font size: {{fontSizePx}}px, font weight: {{fontWeightLabel}}). Expected contrast ratio of {{threshold}}:1 ({{#isLargeText}}large text{{/isLargeText}}{{^isLargeText}}normal text{{/isLargeText}}).',
291
-
292
- contrastMinimum_pass_allAboveThreshold:
293
- 'All computable text meets minimum contrast (AA). Eligible text nodes: {{eligibleTextCount}}. Computable: {{computableTextCount}}.',
294
-
295
- contrastMinimum_notApplicable_noComputableText:
296
- 'No eligible text had computable contrast (eligible text nodes: {{eligibleTextCount}}). See the contrast computability rule for details.',
297
-
298
- contrastMinimum_cantTell_engineFailure:
299
- 'Minimum contrast (AA) could not be determined due to an internal engine error ({{reasonCode}}).',
300
-
301
- // --- DOM Contrast: AAA enhanced (1.4.6)
302
- contrastEnhanced_title: 'Text meets enhanced color contrast (AAA)',
303
- contrastEnhanced_description:
304
- 'Checks that visible text has a contrast ratio of at least 7.0:1 (normal) or 4.5:1 (large), when contrast is computable from CSS.',
305
-
306
- contrastEnhanced_fail_belowThreshold:
307
- 'Element has insufficient color contrast (AAA) of {{ratio}}:1 (foreground: {{foregroundHex}}, background: {{backgroundHex}}, font size: {{fontSizePx}}px, font weight: {{fontWeightLabel}}). Expected contrast ratio of {{threshold}}:1 ({{#isLargeText}}large text{{/isLargeText}}{{^isLargeText}}normal text{{/isLargeText}}).',
308
-
309
- contrastEnhanced_pass_allAboveThreshold:
310
- 'All computable text meets enhanced contrast (AAA). Eligible text nodes: {{eligibleTextCount}}. Computable: {{computableTextCount}}.',
311
-
312
- contrastEnhanced_notApplicable_noComputableText:
313
- 'No eligible text had computable contrast (eligible text nodes: {{eligibleTextCount}}). See the contrast computability rule for details.',
314
-
315
- contrastEnhanced_cantTell_engineFailure:
316
- 'Enhanced contrast (AAA) could not be determined due to an internal engine error ({{reasonCode}}).',
317
-
318
- // --- 1) Text contrast (Minimum) — WCAG 1.4.3 (AA)
319
- dom_textContrastMinimum_title: 'Text must have sufficient contrast (minimum)',
320
- dom_textContrastMinimum_description:
321
- 'Checks visible text contrast against its computed background per WCAG 2.2 SC 1.4.3 (AA), using rendered styles (font size/weight) to determine the required ratio.',
322
-
323
- dom_textContrastMinimum_summary_fail:
324
- 'Insufficient text contrast: {{contrastRatio}}:1 (required {{requiredRatio}}:1). Foreground {{fgColor}} on background {{bgColor}}. Font {{fontSizePx}}px, weight {{fontWeight}}{{#isBold}}, bold{{/isBold}}{{#isLargeText}} (large text){{/isLargeText}}.',
325
- dom_textContrastMinimum_hint_fail:
326
- 'Adjust the foreground or background color so the contrast ratio is at least {{requiredRatio}}:1 for this text size/weight.',
327
-
328
- dom_textContrastMinimum_summary_pass:
329
- 'Text contrast OK: {{contrastRatio}}:1 (required {{requiredRatio}}:1). Foreground {{fgColor}} on background {{bgColor}}. Font {{fontSizePx}}px, weight {{fontWeight}}{{#isBold}}, bold{{/isBold}}{{#isLargeText}} (large text){{/isLargeText}}.',
330
-
331
- dom_textContrastMinimum_summary_cantTell:
332
- 'Could not reliably compute text contrast because the effective background is not deterministically resolvable (e.g. image, gradient, video, canvas, complex transparency, or blending).',
333
- dom_textContrastMinimum_hint_cantTell:
334
- 'Manually verify contrast where text overlays imagery/gradients/transparency; ensure it meets {{requiredRatio}}:1 for the computed text size/weight.',
335
-
336
- // --- 2) Text contrast (Enhanced) — WCAG 1.4.6 (AAA)
337
- dom_textContrastEnhanced_title: 'Text must have sufficient contrast (enhanced)',
338
- dom_textContrastEnhanced_description:
339
- 'Checks visible text contrast against its computed background per WCAG 2.2 SC 1.4.6 (AAA), using rendered styles (font size/weight) to determine the required ratio.',
340
-
341
- dom_textContrastEnhanced_summary_fail:
342
- 'Insufficient enhanced text contrast: {{contrastRatio}}:1 (required {{requiredRatio}}:1). Foreground {{fgColor}} on background {{bgColor}}. Font {{fontSizePx}}px, weight {{fontWeight}}{{#isBold}}, bold{{/isBold}}{{#isLargeText}} (large text){{/isLargeText}}.',
343
- dom_textContrastEnhanced_hint_fail:
344
- 'Adjust the foreground or background color so the contrast ratio is at least {{requiredRatio}}:1 for enhanced (AAA) contrast.',
345
-
346
- dom_textContrastEnhanced_summary_pass:
347
- 'Enhanced text contrast OK: {{contrastRatio}}:1 (required {{requiredRatio}}:1). Foreground {{fgColor}} on background {{bgColor}}. Font {{fontSizePx}}px, weight {{fontWeight}}{{#isBold}}, bold{{/isBold}}{{#isLargeText}} (large text){{/isLargeText}}.',
348
-
349
- dom_textContrastEnhanced_summary_cantTell:
350
- 'Could not reliably compute enhanced text contrast because the effective background is not deterministically resolvable (e.g. image, gradient, video, canvas, complex transparency, or blending).',
351
- dom_textContrastEnhanced_hint_cantTell:
352
- 'Manually verify enhanced (AAA) contrast where text overlays imagery/gradients/transparency; ensure it meets {{requiredRatio}}:1 for the computed text size/weight.',
353
-
354
- // --- 3) Non-text contrast — WCAG 1.4.11 (AA)
355
- dom_nonTextContrast_title: 'UI components and graphics must have sufficient contrast',
356
- dom_nonTextContrast_description:
357
- 'Checks contrast for non-text visual information (UI component boundaries, states, and meaningful graphical objects) per WCAG 2.2 SC 1.4.11 (AA).',
358
-
359
- dom_nonTextContrast_summary_fail:
360
- 'Insufficient non-text contrast: {{contrastRatio}}:1 (required {{requiredRatio}}:1). Foreground {{fgColor}} against background {{bgColor}}. Component: {{componentKind}}{{#componentState}} (state: {{componentState}}){{/componentState}}.',
361
- dom_nonTextContrast_hint_fail:
362
- 'Adjust the component/graphic colors so the contrast ratio is at least {{requiredRatio}}:1 for the perceivable boundary or essential visual information.',
363
-
364
- dom_nonTextContrast_summary_pass:
365
- 'Non-text contrast OK: {{contrastRatio}}:1 (required {{requiredRatio}}:1). Foreground {{fgColor}} against background {{bgColor}}. Component: {{componentKind}}{{#componentState}} (state: {{componentState}}){{/componentState}}.',
366
-
367
- dom_nonTextContrast_summary_cantTell:
368
- 'Could not reliably compute non-text contrast because the effective background or painted pixels are not deterministically resolvable (e.g. image/gradient/video/canvas, complex transparency, or blending).',
369
- dom_nonTextContrast_hint_cantTell:
370
- 'Manually verify the component/graphic contrast against adjacent colors; ensure it meets {{requiredRatio}}:1 for essential non-text visual information.',
371
- contrastEnhanced_pass_allTextMeetsThreshold: 'All computable text meets enhanced contrast (AAA).',
372
- contrastMinimum_pass_allTextMeetsThreshold: 'All computable text meets minimum contrast (AA).',
373
-
374
- contrastComputable_cantTell_notComputable:
375
- 'Contrast could not be computed for this text ({{reasonCode}}).',
376
-
377
- // Title & description
378
- roleImg_textAlternativePresent_title: '[role="img"] must have an accessible text alternative',
379
-
380
- roleImg_textAlternativePresent_description:
381
- 'Checks that elements with role="img" provide an accessible text alternative using aria-label, aria-labelledby, or a title attribute.',
382
-
383
- // Failure summary & hint
384
- roleImg_textAlternativePresent_summary_fail:
385
- 'The element with role="img" does not have an accessible text alternative.',
386
-
387
- roleImg_textAlternativePresent_hint_fail:
388
- 'Provide a text alternative using aria-label, or aria-labelledby that references non-empty text.',
389
-
390
- // --- Target Size (Minimum) — WCAG 2.5.8 (AA)
391
- targetSizeMinimum_title:
392
- 'Pointer targets must be at least 24x24px large, or leave sufficient distance to other targets',
393
- targetSizeMinimum_description:
394
- 'Checks that pointer-operable targets have an effective hit region of at least 24 by 24 CSS pixels, or meet an allowed exception (e.g. sufficient spacing).',
395
-
396
- targetSizeMinimum_summary_fail:
397
- 'One or more pointer targets are smaller than 24×24 CSS px and are too close to another target.',
398
- targetSizeMinimum_hint_fail:
399
- 'Increase the target size to at least 24×24 CSS px or add sufficient spacing from neighboring targets.',
400
- targetSizeMinimum_summary_cantTell_ambiguousSpacing:
401
- 'Target may be too small and too close to another target, but the overlap is near the detection threshold and could not be confidently measured.',
402
- targetSizeMinimum_hint_cantTell_ambiguousSpacing:
403
- 'Manually verify the effective spacing between this target and its neighbor; increase target size or spacing if the overlap is real.',
404
- targetSizeMinimum_summary_cantTell_plausiblyEssential:
405
- 'Target is too small and too close to another target, but may be exempt as part of an essential graphic or image-map region.',
406
- targetSizeMinimum_hint_cantTell_plausiblyEssential:
407
- 'Verify whether this target’s size is genuinely essential to its function (e.g. part of an SVG/canvas/image map); if not, increase target size or spacing.',
408
-
409
- targetSizeMinimum_notApplicable_noTargets:
410
- 'No pointer-operable targets were eligible for evaluation.',
411
- targetSizeMinimum_pass_allOk:
412
- 'All eligible pointer targets meet the minimum size or a permitted exception.',
413
-
414
- // --- aria-hidden-focus
415
- ariaHidden_focus_title: 'ARIA hidden elements must not be focusable',
416
- ariaHidden_focus_description:
417
- 'Checks that aria-hidden="true" elements are not focusable and do not contain focusable descendants.',
418
-
419
- ariaHidden_focus_summary_fail_desc:
420
- 'aria-hidden {{element}} contains {{focusableCount}} focusable element(s).',
421
- ariaHidden_focus_summary_fail_self:
422
- 'aria-hidden {{element}} is focusable ({{focusableCount}} focusable element(s)).',
423
- ariaHidden_focus_summary_fail_self_and_desc:
424
- 'aria-hidden {{element}} is focusable and contains {{descendantFocusableCount}} focusable descendant(s) ({{focusableCount}} focusable element(s) total).',
425
-
426
- ariaHidden_focus_hint_fail:
427
- 'Remove focusability from descendants or remove aria-hidden; ensure focus and accessibility trees stay aligned.',
428
- ariaHidden_focus_summary_cantTell_redirect:
429
- 'aria-hidden {{element}} received focus but focus moved immediately to another element. Verify sentinel/focus-trap behavior.',
430
- ariaHidden_focus_hint_cantTell_redirect:
431
- 'Verify this is an intentional focus sentinel/focus-trap handoff and that keyboard users never remain on hidden focus targets.',
432
-
433
- // --- css-hidden-focus
434
- cssHidden_focus_title: 'Focusable elements must not be visually hidden',
435
- cssHidden_focus_description:
436
- 'Checks that keyboard-focusable elements are not visually hidden by CSS techniques that can leave them in the tab order.',
437
-
438
- cssHidden_focus_summary_cantTell:
439
- 'Focusable {{element}} is visually hidden ({{visibilityHints}}).',
440
- cssHidden_focus_hint_cantTell:
441
- 'Make the element visible when it can receive keyboard focus, or remove it from the tab order until it is visible.',
442
-
443
- linkNamePresent_title: 'Links have an accessible name',
444
- linkNamePresent_description: 'Checks that links expose a non-empty accessible name.',
445
- linkNamePresent_summary_fail: 'This link has no accessible name.',
446
- linkNamePresent_hint_fail:
447
- 'Provide link text or an accessible-name mechanism (for example aria-label) so assistive technologies can identify the link.',
448
- buttonNamePresent_title: 'Buttons have an accessible name',
449
- buttonNamePresent_description: 'Checks that buttons expose a non-empty accessible name.',
450
- buttonNamePresent_summary_fail: 'This button has no accessible name.',
451
- buttonNamePresent_hint_fail:
452
- 'Provide visible button text or a programmatic accessible-name mechanism (for example aria-label) so assistive technologies can identify the button.',
453
-
454
- binaryControlNamePresent_title: 'Binary controls have an accessible name',
455
- binaryControlNamePresent_description:
456
- 'Checks that checkbox, radio, and switch controls expose a non-empty accessible name.',
457
- binaryControlNamePresent_summary_fail: 'This control has no accessible name.',
458
- binaryControlNamePresent_hint_fail:
459
- 'Provide a label, aria-label, aria-labelledby, or other accessible-name mechanism so assistive technologies can identify the control.',
460
- comboboxNamePresent_title: 'Comboboxes have an accessible name',
461
- comboboxNamePresent_description:
462
- 'Checks that elements with role="combobox" expose a non-empty accessible name.',
463
- comboboxNamePresent_summary_fail: 'This combobox has no accessible name.',
464
- comboboxNamePresent_hint_fail:
465
- "Provide aria-label, aria-labelledby, or a title attribute — visible text content is not exposed as this combobox's accessible name.",
466
- dialogNamePresent_title: 'Dialogs have an accessible name',
467
- dialogNamePresent_description:
468
- 'Checks that elements with role="dialog" or role="alertdialog" expose a non-empty accessible name.',
469
- dialogNamePresent_summary_fail: 'This dialog has no accessible name.',
470
- dialogNamePresent_hint_fail:
471
- 'Provide aria-labelledby (preferred) or aria-label so assistive technologies can announce the dialog.',
472
- menuitemNamePresent_title: 'Menu items have an accessible name',
473
- menuitemNamePresent_description:
474
- 'Checks that menu items (role="menuitem*", including checkbox/radio variants) expose a non-empty accessible name.',
475
- menuitemNamePresent_summary_fail: 'This menu item has no accessible name.',
476
- menuitemNamePresent_hint_fail:
477
- 'Provide visible text that is not hidden from assistive technologies, or provide aria-label or aria-labelledby.',
478
- tabNamePresent_title: 'Tabs have an accessible name',
479
- tabNamePresent_description:
480
- 'Checks that elements with role="tab" expose a non-empty accessible name.',
481
- tabNamePresent_summary_fail: 'This tab has no accessible name.',
482
- tabNamePresent_hint_fail:
483
- 'Provide tab text that is not hidden from assistive technologies, or provide aria-label or aria-labelledby.',
484
- sliderNamePresent_title: 'Sliders have an accessible name',
485
- sliderNamePresent_description:
486
- 'Checks that sliders (input[type="range"] and role="slider") expose a non-empty accessible name.',
487
- sliderNamePresent_summary_fail: 'This slider has no accessible name.',
488
- sliderNamePresent_hint_fail:
489
- 'Provide a label, aria-label, or aria-labelledby so assistive technologies can identify the slider.',
490
- textboxNamePresent_title: 'Textboxes have an accessible name',
491
- textboxNamePresent_description:
492
- 'Checks that elements with role="textbox" expose a non-empty accessible name.',
493
- textboxNamePresent_summary_fail: 'This textbox has no accessible name.',
494
- textboxNamePresent_hint_fail:
495
- "Provide aria-label, aria-labelledby, or a title attribute — visible text content is not exposed as this textbox's accessible name.",
496
- searchboxNamePresent_title: 'Searchboxes have an accessible name',
497
- searchboxNamePresent_description:
498
- 'Checks that elements with role="searchbox" expose a non-empty accessible name.',
499
- searchboxNamePresent_summary_fail: 'This searchbox has no accessible name.',
500
- searchboxNamePresent_hint_fail:
501
- "Provide aria-label, aria-labelledby, or a title attribute — visible text content is not exposed as this searchbox's accessible name.",
502
- spinbuttonNamePresent_title: 'Spinbuttons have an accessible name',
503
- spinbuttonNamePresent_description:
504
- 'Checks that elements with role="spinbutton" expose a non-empty accessible name.',
505
- spinbuttonNamePresent_summary_fail: 'This spinbutton has no accessible name.',
506
- spinbuttonNamePresent_hint_fail:
507
- "Provide aria-label, aria-labelledby, or a title attribute — visible text content is not exposed as this spinbutton's accessible name.",
508
- listboxNamePresent_title: 'Listboxes have an accessible name',
509
- listboxNamePresent_description:
510
- 'Checks that elements with role="listbox" expose a non-empty accessible name.',
511
- listboxNamePresent_summary_fail: 'This listbox has no accessible name.',
512
- listboxNamePresent_hint_fail:
513
- "Provide aria-label, aria-labelledby, or a title attribute — visible text content is not exposed as this listbox's accessible name.",
514
- optionNamePresent_title: 'Options have an accessible name',
515
- optionNamePresent_description:
516
- 'Checks that elements with role="option" expose a non-empty accessible name.',
517
- optionNamePresent_summary_fail: 'This option has no accessible name.',
518
- optionNamePresent_hint_fail:
519
- 'Provide option text that is not hidden from assistive technologies, or provide aria-label or aria-labelledby.',
520
- treeitemNamePresent_title: 'Tree items have an accessible name',
521
- treeitemNamePresent_description:
522
- 'Checks that elements with role="treeitem" expose a non-empty accessible name.',
523
- treeitemNamePresent_summary_fail: 'This tree item has no accessible name.',
524
- treeitemNamePresent_hint_fail:
525
- 'Provide tree item text that is not hidden from assistive technologies, or provide aria-label or aria-labelledby.',
526
- ariaRoleNamePresent_title: 'ARIA widget/container roles have an accessible name',
527
- ariaRoleNamePresent_description:
528
- 'Checks that selected ARIA widget/container roles expose a non-empty accessible name.',
529
- ariaRoleNamePresent_summary_fail: 'This element has no accessible name.',
530
- ariaRoleNamePresent_hint_fail:
531
- 'Provide aria-label or aria-labelledby (preferred), or a non-empty title attribute.',
532
-
533
- /* =========================
534
- * Composite rule summaries
535
- * ========================= */
536
- composite_rollup_summary: 'Composite rule result: {{reasonCode}} ({{testCount}} checks)',
537
-
538
- /* =========================
539
- * WCAG 1.1.1 – Non-text content
540
- * ========================= */
541
- 'catalog.rules.wcag_111_non_text_content.title': 'Non-text content: text alternatives',
542
- 'catalog.rules.wcag_111_non_text_content.description':
543
- 'Rollup of checks ensuring non-text content has an appropriate text alternative.',
544
-
545
- /* =========================
546
- * WCAG 1.2.1 – Audio-only / Video-only
547
- * ========================= */
548
- 'catalog.rules.wcag_121_prerecorded_transcript.title':
549
- 'Audio-only and video-only (prerecorded): transcript',
550
- 'catalog.rules.wcag_121_prerecorded_transcript.description':
551
- 'Rollup of checks for transcript availability for prerecorded audio-only or video-only media.',
552
-
553
- /* =========================
554
- * WCAG 1.4.3 – Contrast (Minimum)
555
- * ========================= */
556
- 'catalog.rules.wcag_143_contrast_minimum.title': 'Contrast: minimum',
557
- 'catalog.rules.wcag_143_contrast_minimum.description':
558
- 'Rollup of checks for minimum text contrast.',
559
-
560
- /* =========================
561
- * WCAG 1.4.6 – Contrast (Enhanced)
562
- * ========================= */
563
- 'catalog.rules.wcag_146_contrast_enhanced.title': 'Contrast: enhanced',
564
- 'catalog.rules.wcag_146_contrast_enhanced.description':
565
- 'Rollup of checks for enhanced text contrast.',
566
-
567
- /* =========================
568
- * WCAG 2.4.2 – Page titled
569
- * ========================= */
570
- 'catalog.rules.wcag_242_page_titled.title': 'Page titled',
571
- 'catalog.rules.wcag_242_page_titled.description':
572
- 'Rollup of checks ensuring documents have a meaningful page title.',
573
-
574
- /* =========================
575
- * WCAG 2.4.7 – Focus visible
576
- * ========================= */
577
- 'catalog.rules.wcag_247_focus_visible.title': 'Focus visible',
578
- 'catalog.rules.wcag_247_focus_visible.description':
579
- 'Rollup of checks ensuring keyboard focus is not hidden and remains perceivable.',
580
-
581
- /* =========================
582
- * WCAG 2.5.8 – Target size (Minimum)
583
- * ========================= */
584
- 'catalog.rules.wcag_258_target_size_minimum.title': 'Target size: minimum',
585
- 'catalog.rules.wcag_258_target_size_minimum.description':
586
- 'Rollup of checks ensuring pointer targets meet minimum size requirements.',
587
-
588
- /* =========================
589
- * WCAG 3.1.1 – Language of page
590
- * ========================= */
591
- 'catalog.rules.wcag_311_language_of_page.title': 'Language of page',
592
- 'catalog.rules.wcag_311_language_of_page.description':
593
- 'Rollup of checks ensuring the page language is specified.',
594
-
595
- /* =========================
596
- * WCAG 4.1.2 – Name, role, value
597
- * ========================= */
598
- 'catalog.rules.wcag_412_name.title': 'Name, role, value: accessible name',
599
- 'catalog.rules.wcag_412_name.description':
600
- 'Rollup of checks that common interactive elements expose a non-empty accessible name.',
601
- labelInName_title: 'Label in Name: accessible name contains visible text',
602
- labelInName_description:
603
- 'Checks that when a control has a visible text label, the accessible name contains that visible label text (WCAG 2.5.3).',
604
- labelInName_summary_fail:
605
- '{{element}}: visible label "{{visibleLabel}}" (from {{labelSource}}) is not included in the accessible name (from {{nameMechanism}}).',
606
- labelInName_hint_fail:
607
- 'Update aria-label/aria-labelledby (or the visible label text) so the accessible name includes the visible label wording.',
608
-
609
- /* =========================
610
- * ARIA validity family
611
- * ========================= */
612
- ariaRolesValid_title: 'role attribute must be a valid, non-abstract ARIA role',
613
- ariaRolesValid_description:
614
- 'Checks that an explicit role="" attribute resolves to a real, non-abstract WAI-ARIA role.',
615
- ariaRolesValid_summary_invalid: 'role="{{role}}" is not a recognized ARIA role.',
616
- ariaRolesValid_hint_invalid:
617
- 'Use a valid ARIA role token, or remove the role attribute if none applies.',
618
- ariaRolesValid_summary_abstract:
619
- 'role="{{role}}" is an abstract ARIA role, which must not be used directly.',
620
- ariaRolesValid_hint_abstract:
621
- 'Replace this abstract role with a concrete role appropriate for the widget/structure.',
622
-
623
- ariaDeprecatedRole_title:
624
- 'role attribute must not use a deprecated or author-prohibited ARIA role',
625
- ariaDeprecatedRole_description:
626
- 'Checks that an explicit role="" attribute does not use a role deprecated by the WAI-ARIA specification, or one reserved for user-agent-internal use only (e.g. role="generic").',
627
- ariaDeprecatedRole_summary_fail:
628
- 'This element uses role="{{role}}", which authors must not explicitly declare.',
629
- ariaDeprecatedRole_hint_fail: '{{guidance}}',
630
-
631
- ariaValidAttr_title: 'aria-* attributes must be real, defined ARIA attributes',
632
- ariaValidAttr_description:
633
- 'Checks that every aria-* attribute name present in the DOM is a real attribute defined by the WAI-ARIA specification.',
634
- ariaValidAttr_summary_fail: '{{attr}} is not a recognized ARIA attribute.',
635
- ariaValidAttr_hint_fail:
636
- 'Correct the attribute name (check for typos), or remove it if not needed.',
637
-
638
- ariaValidAttrValue_title: 'aria-* attribute values must match their declared type',
639
- ariaValidAttrValue_description:
640
- 'Checks that every recognized aria-* attribute has a value conforming to its WAI-ARIA-declared value type (boolean, tristate, token, integer, number, or ID reference).',
641
- ariaValidAttrValue_summary_fail: '{{attr}}="{{value}}" is not a valid value for this attribute.',
642
- ariaValidAttrValue_hint_fail:
643
- 'Use a value that matches the attribute’s expected type (see the WAI-ARIA specification for this attribute).',
644
-
645
- ariaAllowedAttr_title: 'aria-* attributes must be permitted for the element’s role',
646
- ariaAllowedAttr_description:
647
- 'Checks that every recognized aria-* attribute present on an element with an explicit role is either globally supported or supported by that role.',
648
- ariaAllowedAttr_summary_fail: '{{attr}} is not permitted on role="{{role}}".',
649
- ariaAllowedAttr_hint_fail: 'Remove this attribute, or use a role that supports it.',
650
-
651
- ariaProhibitedAttr_title: 'ARIA naming attributes must not be used on roles that prohibit them',
652
- ariaProhibitedAttr_description:
653
- 'Checks that aria-label/aria-labelledby are not present on WAI-ARIA roles whose specification explicitly prohibits ARIA naming (e.g. generic, emphasis, strong, paragraph).',
654
- ariaProhibitedAttr_summary_fail: '{{attr}} is prohibited on role="{{role}}".',
655
- ariaProhibitedAttr_hint_fail:
656
- 'Remove this attribute; this role must not carry an accessible name.',
657
- ariaProhibitedAttr_summary_fail_roleless:
658
- 'This {{element}} has no role and no other accessible-name source, so {{attr}} is not reliably exposed to assistive technology.',
659
- ariaProhibitedAttr_hint_fail_roleless:
660
- 'Give this element a role that supports an accessible name (e.g. role="img"/"button"), or remove this attribute if it serves no purpose without one.',
661
- ariaProhibitedAttr_summary_cantTell_roleless:
662
- "This {{element}} has no role, so {{attr}} may not be exposed as its accessible name by assistive technology — but the element's own content already provides one.",
663
- ariaProhibitedAttr_hint_cantTell_roleless:
664
- 'Verify whether the existing text content already serves as this element\'s label; if so the naming attribute is redundant, otherwise give the element a role that supports naming (e.g. role="img").',
665
-
666
- ariaRequiredAttr_title: 'Roles with a required ARIA state/property must carry it',
667
- ariaRequiredAttr_description:
668
- 'Checks that elements with an explicit role carry every unambiguous, context-independent required aria-* state/property for that role (e.g. role="checkbox" must have aria-checked).',
669
- ariaRequiredAttr_summary_fail: '{{attr}} is required for role="{{role}}", but is missing.',
670
- ariaRequiredAttr_hint_fail: 'Add this attribute with a valid value for this role.',
671
-
672
- ariaAllowedRole_title: 'Explicit role must be permitted for its host element',
673
- ariaAllowedRole_description:
674
- 'Checks that an explicit role="" attribute is one of the roles the ARIA-in-HTML specification permits for the host element (e.g. role="tab" is not permitted on <nav>).',
675
- ariaAllowedRole_summary_fail: 'role="{{role}}" is not permitted on <{{element}}>.',
676
- ariaAllowedRole_hint_fail: 'Use a role permitted for this element, or change the host element.',
677
-
678
- ariaRequiredChildren_title: 'Container roles must own at least one required child role',
679
- ariaRequiredChildren_description:
680
- 'Checks that container roles with a documented "required owned elements" entry (list, listbox, menu, radiogroup, table, grid, tablist, tree, row, ...) contain at least one descendant or aria-owns-referenced element with an acceptable owned role.',
681
- ariaRequiredChildren_summary_fail:
682
- 'role="{{role}}" has no owned child with one of the required roles: {{requiredRoles}}.',
683
- ariaRequiredChildren_hint_fail:
684
- 'Add a descendant (or aria-owns-referenced element) with one of the required owned roles.',
685
-
686
- ariaProhibitedChildren_title: 'Container roles must not own a child with a disallowed role',
687
- ariaProhibitedChildren_description:
688
- "Checks that every accessible-tree-owned child of a container role (list, listbox, menu, menubar, radiogroup, rowgroup, table, grid, treegrid, tablist, tree, row) has one of that role's allowed owned roles.",
689
- ariaProhibitedChildren_summary_fail:
690
- 'This element has role="{{childRole}}", which is not an allowed owned child of the enclosing role="{{containerRole}}" container.',
691
- ariaProhibitedChildren_hint_fail:
692
- "Remove or change this role so it matches one of the container's allowed owned roles ({{allowedRoles}}), or move this element outside the {{containerRole}} container.",
693
- ariaProhibitedChildren_summary_fail_roleless:
694
- 'This element has no explicit role but carries {{attr}}, making it a real accessible-tree node that is not an allowed owned child of the enclosing role="{{containerRole}}" container.',
695
- ariaProhibitedChildren_hint_fail_roleless:
696
- 'Remove {{attr}} (or the role="{{containerRole}}" container ownership), or give this element role="presentation"/"none" if it isn\'t meant to be its own accessible-tree node.',
697
- ariaProhibitedChildren_summary_fail_native_focusable:
698
- 'This element has no explicit role but is natively focusable, making it a real accessible-tree node that is not an allowed owned child of the enclosing role="{{containerRole}}" container.',
699
- ariaProhibitedChildren_hint_fail_native_focusable:
700
- 'Give this element role="presentation"/"none", remove its native focusability (e.g. drop the href/tabindex-granting attribute), or move it outside the {{containerRole}} container.',
701
-
702
- ariaRequiredParent_title: 'Roles requiring a specific context role must be in that context',
703
- ariaRequiredParent_description:
704
- 'Checks that roles with a documented "required context role" entry (listitem, option, tab, treeitem, row, cell, ...) have an ancestor or aria-owns owner with an acceptable context role.',
705
- ariaRequiredParent_summary_fail:
706
- 'role="{{role}}" requires a context role of one of: {{requiredRoles}}, which was not found.',
707
- ariaRequiredParent_hint_fail:
708
- 'Place this element inside (or aria-owns-reference it from) an element with an acceptable context role.',
709
-
710
- deprecatedElements_title: 'Obsolete non-stoppable elements (<blink>, <marquee>) must not be used',
711
- deprecatedElements_description:
712
- 'Checks that deprecated, non-standard HTML elements whose blinking/scrolling content cannot be paused, stopped, or hidden by the user (<blink>, <marquee>) are not present.',
713
- deprecatedElements_summary_fail:
714
- '<{{element}}> content cannot be paused, stopped, or hidden by the user.',
715
- deprecatedElements_hint_fail:
716
- 'Remove this element; use static content, or an animation with a user-facing pause/stop control, instead.',
717
-
718
- iframeNamePresent_title: 'Frames have an accessible name',
719
- iframeNamePresent_description:
720
- 'Checks that <iframe>/<frame> elements expose a non-empty accessible name via aria-label, aria-labelledby, or the title attribute.',
721
- iframeNamePresent_summary_fail: 'This <{{element}}> has no accessible name.',
722
- iframeNamePresent_hint_fail:
723
- 'Add a title attribute (or aria-label/aria-labelledby) describing the frame’s content or purpose.',
724
-
725
- iframeTitleUnique_title: 'Frame titles must be unique',
726
- iframeTitleUnique_description:
727
- 'Checks that no two <iframe>/<frame> elements in scope share the same title attribute value.',
728
- iframeTitleUnique_summary_fail:
729
- 'This <{{element}}>\'s title "{{title}}" is not unique among the frames on this page.',
730
- iframeTitleUnique_hint_fail:
731
- 'Give each frame a distinct title describing its specific content or purpose.',
732
-
733
- iframeFocusableContent_title: 'Frames with tabindex="-1" must not contain focusable content',
734
- iframeFocusableContent_description:
735
- 'Checks that same-origin <iframe>/<frame> elements with tabindex="-1" do not contain focusable content, since browsers do not propagate that restriction into the frame’s embedded document.',
736
- iframeFocusableContent_summary_fail:
737
- 'This <{{element}}> has tabindex="-1" but its content contains focusable elements, which remain reachable by keyboard.',
738
- iframeFocusableContent_hint_fail:
739
- 'Remove focusable content from the frame, or remove tabindex="-1" if the frame is meant to be reachable.',
740
-
741
- tableHeadersAttrValid_title: 'Table cell "headers" attribute must reference valid header cells',
742
- tableHeadersAttrValid_description:
743
- 'Checks that each id in a <td>/<th> headers attribute resolves to a <th> element within the same table (not missing, not a non-th element, not itself).',
744
- tableHeadersAttrValid_summary_fail:
745
- "This <{{element}}>'s headers attribute references invalid header cell(s): {{invalidIds}}.",
746
- tableHeadersAttrValid_hint_fail:
747
- 'Update the headers attribute so every id refers to a <th> element within the same table.',
748
-
749
- tableThHasDataCells_title: '<th> elements must describe at least one data cell',
750
- tableThHasDataCells_description:
751
- 'Checks that a table containing <th> elements also contains at least one <td> data cell for those headers to describe.',
752
- tableThHasDataCells_summary_fail:
753
- 'This table has header cells but no data cells for them to describe.',
754
- tableThHasDataCells_hint_fail:
755
- 'Add data cells (<td>) to the table, or remove the header cells if the table has no data.',
756
-
757
- ariaHiddenBody_title: 'The document <body> must not be aria-hidden',
758
- ariaHiddenBody_description:
759
- 'Checks that <body> does not have aria-hidden="true", which would remove the entire page from the accessibility tree.',
760
- ariaHiddenBody_summary_fail:
761
- 'The document body has aria-hidden="true", which hides the entire page from assistive technologies.',
762
- ariaHiddenBody_hint_fail:
763
- 'Remove aria-hidden from <body>. Hide specific elements instead, if that was the intent.',
764
-
765
- listChildrenValid_title: 'Lists must only directly contain list items',
766
- listChildrenValid_description:
767
- 'Checks that <ul>/<ol> elements only have <li>, <script>, or <template> as direct children.',
768
- listChildrenValid_summary_fail:
769
- 'This <{{element}}> contains a direct child that is not a list item: {{invalidChildren}}.',
770
- listChildrenValid_hint_fail:
771
- 'Only use <li> (or <script>/<template>) as direct children of <ul>/<ol>; move other markup inside an <li>.',
772
-
773
- listitemParentValid_title: 'List items must be inside a list container',
774
- listitemParentValid_description:
775
- 'Checks that <li> elements are contained by <ul>, <ol>, or an element with role="list".',
776
- listitemParentValid_summary_fail:
777
- "This list item's parent (<{{parentElement}}>) is not a list container.",
778
- listitemParentValid_hint_fail:
779
- 'Place this <li> inside a <ul>/<ol>, or give its parent role="list".',
780
-
781
- definitionListChildrenValid_title: 'Description lists must be structured correctly',
782
- definitionListChildrenValid_description:
783
- 'Checks that <dl> elements only directly contain <dt>/<dd> groups (optionally wrapped in one <div>), <script>, <template>, or <style>.',
784
- definitionListChildrenValid_summary_fail_invalidChild:
785
- 'This description list contains a direct or wrapped child that is not part of a dt/dd group: {{invalidChildren}}.',
786
- definitionListChildrenValid_hint_fail_invalidChild:
787
- 'Only use <dt>/<dd> (optionally wrapped in one <div>), <script>, <template>, or <style> inside <dl>.',
788
- definitionListChildrenValid_summary_fail_noDtDd:
789
- 'This description list has no <dt>/<dd> term-definition group.',
790
- definitionListChildrenValid_hint_fail_noDtDd: 'Add at least one <dt>/<dd> pair inside this <dl>.',
791
-
792
- dlitemParentValid_title: 'Description-list items must be inside a description list',
793
- dlitemParentValid_description:
794
- 'Checks that <dt>/<dd> elements are contained by a <dl>, directly or via one wrapping <div>.',
795
- dlitemParentValid_summary_fail:
796
- "This <{{element}}>'s parent (<{{parentElement}}>) is not a description list.",
797
- dlitemParentValid_hint_fail:
798
- 'Place this <dt>/<dd> inside a <dl>, directly or wrapped in a single <div>.',
799
-
800
- duplicateIdAria_title: 'IDs referenced by ARIA must be unique',
801
- duplicateIdAria_description:
802
- 'Checks that any id value referenced by an ARIA ID-reference attribute (aria-labelledby, aria-describedby, aria-owns, aria-controls, aria-activedescendant, aria-flowto, aria-errormessage, aria-details) is unique in the document.',
803
- duplicateIdAria_summary_fail:
804
- 'The id "{{id}}" is referenced by an ARIA attribute but is used by {{duplicateCount}} elements.',
805
- duplicateIdAria_hint_fail: 'Make ids referenced by ARIA attributes unique within the document.',
806
-
807
- summaryNamePresent_title: 'Summary elements have an accessible name',
808
- summaryNamePresent_description:
809
- 'Checks that <summary> elements expose a non-empty accessible name.',
810
- summaryNamePresent_summary_fail: 'This summary has no accessible name.',
811
- summaryNamePresent_hint_fail:
812
- 'Provide summary text that is not hidden from assistive technologies, or provide aria-label or aria-labelledby.',
813
-
814
- metaViewportZoomEnabled_title: 'Viewport meta tag must not disable zoom',
815
- metaViewportZoomEnabled_description:
816
- 'Checks that <meta name="viewport"> does not set user-scalable=no or maximum-scale below 2 (200%).',
817
- metaViewportZoomEnabled_summary_fail:
818
- "This viewport meta tag restricts the user's ability to zoom ({{reasons}}).",
819
- metaViewportZoomEnabled_hint_fail:
820
- 'Remove user-scalable=no and any maximum-scale below 2 from the viewport meta content.',
821
-
822
- metaRefreshTimingAbsent_title: 'Page must not use a timed meta refresh',
823
- metaRefreshTimingAbsent_description:
824
- 'Checks that <meta http-equiv="refresh"> does not impose a positive delay of 20 hours or less.',
825
- metaRefreshTimingAbsent_summary_fail:
826
- 'This page refreshes itself automatically after {{delay}} seconds.',
827
- metaRefreshTimingAbsent_hint_fail:
828
- 'Remove the timed meta refresh, or provide a way for users to turn it off, extend it, or pause it before it triggers.',
829
-
830
- meterNamePresent_title: 'Meters have an accessible name',
831
- meterNamePresent_description:
832
- 'Checks that elements with role="meter" expose a non-empty accessible name.',
833
- meterNamePresent_summary_fail: 'This meter has no accessible name.',
834
- meterNamePresent_hint_fail:
835
- "Provide aria-label, aria-labelledby, or a title attribute — visible text content is not exposed as this meter's accessible name.",
836
-
837
- progressbarNamePresent_title: 'Progress bars have an accessible name',
838
- progressbarNamePresent_description:
839
- 'Checks that elements with role="progressbar" expose a non-empty accessible name.',
840
- progressbarNamePresent_summary_fail: 'This progress bar has no accessible name.',
841
- progressbarNamePresent_hint_fail:
842
- "Provide aria-label, aria-labelledby, or a title attribute — visible text content is not exposed as this progress bar's accessible name.",
843
-
844
- tooltipNamePresent_title: 'Tooltips have an accessible name',
845
- tooltipNamePresent_description:
846
- 'Checks that elements with role="tooltip" expose a non-empty accessible name.',
847
- tooltipNamePresent_summary_fail: 'This tooltip has no accessible name.',
848
- tooltipNamePresent_hint_fail:
849
- 'Provide tooltip text that is not hidden from assistive technologies, or provide aria-label or aria-labelledby.',
850
-
851
- serverSideImageMapAbsent_title: 'Images must not use a server-side image map',
852
- serverSideImageMapAbsent_description:
853
- 'Checks that <img> elements do not carry the ismap attribute (server-side image maps have no keyboard-operable equivalent).',
854
- serverSideImageMapAbsent_summary_fail:
855
- 'This image uses a server-side image map, which has no keyboard-operable equivalent.',
856
- serverSideImageMapAbsent_hint_fail:
857
- 'Replace the server-side image map (ismap) with a client-side image map (<map>/<area>) or separate accessible links/buttons.',
858
-
859
- formControlSingleLabel_title: 'Form controls must not have multiple labels',
860
- formControlSingleLabel_description:
861
- 'Checks that a form control is associated with at most one <label> (by wrapping or by label[for]).',
862
- formControlSingleLabel_summary_fail:
863
- 'This <{{element}}> is associated with {{labelCount}} labels.',
864
- formControlSingleLabel_hint_fail:
865
- 'Keep only one <label> per form control (either wrapping it or referencing it via for/id).',
866
-
867
- nestedInteractiveControlsAbsent_title: 'Interactive controls must not be nested',
868
- nestedInteractiveControlsAbsent_description:
869
- 'Checks that an interactive control (link, button, form control, or ARIA widget role) does not contain another interactive control.',
870
- nestedInteractiveControlsAbsent_summary_fail:
871
- 'This <{{element}}> contains one or more nested interactive controls: {{nestedElements}}.',
872
- nestedInteractiveControlsAbsent_hint_fail:
873
- 'Move the nested interactive control(s) outside this element; nested interactive controls are not reliably operable via assistive technology.',
874
-
875
- bypassBlocksPresent_title: 'Page must provide a way to bypass repeated blocks',
876
- bypassBlocksPresent_description:
877
- 'Checks that the page has at least one recognized WCAG 2.4.1 bypass-blocks mechanism: a main landmark, a working same-page anchor link, or a heading.',
878
- bypassBlocksPresent_summary_fail:
879
- 'This page has no recognized way to bypass repeated blocks of content.',
880
- bypassBlocksPresent_hint_fail:
881
- 'Add a main landmark (<main> or role="main"), a working "skip to content" link, or heading elements that assistive technology can use to jump past repeated content.',
882
-
883
- landmarkBannerIsTopLevel_title: 'Banner landmark must be top-level',
884
- landmarkBannerIsTopLevel_description:
885
- 'Checks that the banner landmark (role="banner" or a non-nested <header>) is not nested inside another landmark region.',
886
- landmarkBannerIsTopLevel_summary_cantTell:
887
- 'This banner landmark is nested inside another landmark region.',
888
- landmarkBannerIsTopLevel_hint_cantTell:
889
- 'Move the banner landmark (header/role="banner") so it is not contained by another landmark; a banner should be a top-level region of the page.',
890
-
891
- landmarkContentinfoIsTopLevel_title: 'Contentinfo landmark must be top-level',
892
- landmarkContentinfoIsTopLevel_description:
893
- 'Checks that the contentinfo landmark (role="contentinfo" or a non-nested <footer>) is not nested inside another landmark region.',
894
- landmarkContentinfoIsTopLevel_summary_cantTell:
895
- 'This contentinfo landmark is nested inside another landmark region.',
896
- landmarkContentinfoIsTopLevel_hint_cantTell:
897
- 'Move the contentinfo landmark (footer/role="contentinfo") so it is not contained by another landmark; contentinfo should be a top-level region of the page.',
898
-
899
- landmarkMainIsTopLevel_title: 'Main landmark must be top-level',
900
- landmarkMainIsTopLevel_description:
901
- 'Checks that the main landmark (role="main" or <main>) is not nested inside another landmark region.',
902
- landmarkMainIsTopLevel_summary_cantTell:
903
- 'This main landmark is nested inside another landmark region.',
904
- landmarkMainIsTopLevel_hint_cantTell:
905
- 'Move the main landmark (<main>/role="main") so it is not contained by another landmark; main should be a top-level region of the page.',
906
-
907
- landmarkNoDuplicateBanner_title: 'Page must not have more than one banner landmark',
908
- landmarkNoDuplicateBanner_description:
909
- 'Checks that at most one banner landmark (role="banner" or a non-nested <header>) exists on the page.',
910
- landmarkNoDuplicateBanner_summary_cantTell: 'This page has more than one banner landmark.',
911
- landmarkNoDuplicateBanner_hint_cantTell:
912
- 'Keep only one banner landmark (header/role="banner") per page.',
913
-
914
- landmarkNoDuplicateContentinfo_title: 'Page must not have more than one contentinfo landmark',
915
- landmarkNoDuplicateContentinfo_description:
916
- 'Checks that at most one contentinfo landmark (role="contentinfo" or a non-nested <footer>) exists on the page.',
917
- landmarkNoDuplicateContentinfo_summary_cantTell:
918
- 'This page has more than one contentinfo landmark.',
919
- landmarkNoDuplicateContentinfo_hint_cantTell:
920
- 'Keep only one contentinfo landmark (footer/role="contentinfo") per page.',
921
-
922
- landmarkNoDuplicateMain_title: 'Page must not have more than one main landmark',
923
- landmarkNoDuplicateMain_description:
924
- 'Checks that at most one main landmark (role="main" or <main>) exists on the page.',
925
- landmarkNoDuplicateMain_summary_cantTell: 'This page has more than one main landmark.',
926
- landmarkNoDuplicateMain_hint_cantTell:
927
- 'Keep only one main landmark (<main>/role="main") per page.',
928
-
929
- landmarkOneMain_title: 'Page should have a main landmark',
930
- landmarkOneMain_description:
931
- 'Checks that the page has at least one main landmark (role="main" or <main>).',
932
- landmarkOneMain_summary_cantTell_missing: 'This page has no main landmark.',
933
- landmarkOneMain_hint_cantTell_missing:
934
- 'Add a main landmark (<main> or role="main") around the page\'s primary content.',
935
-
936
- landmarkUnique_title: 'Landmarks with the same role must have unique names',
937
- landmarkUnique_description:
938
- 'Checks that when two or more landmarks share the same role, each has a distinct accessible name.',
939
- landmarkUnique_summary_cantTell_duplicateName:
940
- 'This {{role}} landmark shares its accessible name with another {{role}} landmark.',
941
- landmarkUnique_summary_cantTell_bothUnnamed:
942
- 'This {{role}} landmark has no accessible name, and more than one unnamed {{role}} landmark exists on this page.',
943
- landmarkUnique_hint_cantTell:
944
- 'Give each {{role}} landmark a distinct name via aria-label or aria-labelledby.',
945
-
946
- emptyHeading_title: 'Headings must not be empty',
947
- emptyHeading_description:
948
- 'Checks that heading elements (<h1>-<h6> or role="heading") have a non-empty accessible name.',
949
- emptyHeading_summary_cantTell: 'This heading has no accessible name.',
950
- emptyHeading_hint_cantTell:
951
- 'Add text content (or aria-label/aria-labelledby) to this heading, or remove it if it is not needed.',
952
-
953
- headingOrder_title: 'Heading levels must not skip a level',
954
- headingOrder_description:
955
- 'Checks that heading levels increase by at most one at a time in document order.',
956
- headingOrder_summary_cantTell:
957
- 'This heading jumps from level {{fromLevel}} to level {{toLevel}}, skipping a level.',
958
- headingOrder_hint_cantTell:
959
- 'Use consecutive heading levels (do not skip a level when going deeper) so the document outline stays predictable.',
960
-
961
- pageHasHeadingOne_title: 'Page should have a level-one heading',
962
- pageHasHeadingOne_description:
963
- 'Checks that the page has at least one level-one heading (<h1> or role="heading" with aria-level="1").',
964
- pageHasHeadingOne_summary_cantTell: 'This page has no level-one heading.',
965
- pageHasHeadingOne_hint_cantTell:
966
- 'Add a level-one heading (<h1> or role="heading" aria-level="1") that identifies the page\'s main content.',
967
-
968
- accesskeys_title: 'accesskey values must be unique',
969
- accesskeys_description:
970
- 'Checks that no two elements on the page share the same accesskey attribute value.',
971
- accesskeys_summary_cantTell:
972
- "This element's accesskey is shared with another element on the page.",
973
- accesskeys_hint_cantTell: 'Make each accesskey value unique across the page.',
974
-
975
- scopeAttrValid_title: 'scope attribute must have a valid value',
976
- scopeAttrValid_description: 'Checks that scope="..." is one of row, col, rowgroup, or colgroup.',
977
- scopeAttrValid_summary_cantTell: 'This scope attribute value is not recognized.',
978
- scopeAttrValid_hint_cantTell:
979
- 'Use one of row, col, rowgroup, or colgroup for the scope attribute.',
980
-
981
- tabindex_title: 'tabindex should not be greater than 0',
982
- tabindex_description: 'Checks that tabindex values are 0 or negative, not a positive number.',
983
- tabindex_summary_cantTell:
984
- 'This element has a positive tabindex, overriding the natural tab order.',
985
- tabindex_hint_cantTell:
986
- 'Use tabindex="0" (or a negative value to remove from tab order) instead of a positive number; fix the DOM order if a different tab order is needed.',
987
-
988
- emptyTableHeader_title: 'Table header cells must not be empty',
989
- emptyTableHeader_description:
990
- 'Checks that <th> elements have visible text content — a header named only via aria-label/aria-labelledby is also flagged, since real screen-reader/browser support for that is inconsistent.',
991
- emptyTableHeader_summary_cantTell: 'This table header cell has no accessible name.',
992
- emptyTableHeader_hint_cantTell:
993
- 'Add text content (or aria-label/aria-labelledby) to this header cell, or remove it if it is not needed.',
994
- emptyTableHeader_summary_cantTell_ariaOnly:
995
- 'This table header cell has no visible text — its only accessible name comes from aria-label/aria-labelledby, which real screen-reader/browser combinations (e.g. NVDA+Firefox, iOS VoiceOver+Safari) are known to ignore on <th> elements.',
996
- emptyTableHeader_hint_cantTell_ariaOnly:
997
- 'Add visible text content to this header cell (in addition to, or instead of, aria-label/aria-labelledby) — visible text is the only naming mechanism confirmed to work across tested screen readers.',
998
-
999
- labelTitleOnly_title: 'Form controls should not use title as their only label',
1000
- labelTitleOnly_description:
1001
- 'Checks that a form control with a title attribute also has a real label (label element, aria-label, or aria-labelledby).',
1002
- labelTitleOnly_summary_cantTell:
1003
- 'This form control relies on the title attribute as its only label.',
1004
- labelTitleOnly_hint_cantTell:
1005
- 'Add a visible <label> (or aria-label/aria-labelledby) in addition to, or instead of, the title attribute.',
1006
-
1007
- imageRedundantAlt_title: 'Image alt text must not duplicate adjacent visible text',
1008
- imageRedundantAlt_description:
1009
- 'Checks that an <img> alt text is not identical to other visible text already present in its immediate parent element.',
1010
- imageRedundantAlt_summary_cantTell:
1011
- "This image's alt text duplicates other visible text right next to it.",
1012
- imageRedundantAlt_hint_cantTell:
1013
- 'Make the alt text empty (alt="") if the image is purely decorative alongside the text, or remove the redundant duplication.',
1014
-
1015
- tableDuplicateName_title: 'Table caption must not duplicate its summary attribute',
1016
- tableDuplicateName_description:
1017
- "Checks that a <table>'s <caption> text is not identical to its (deprecated) summary attribute.",
1018
- tableDuplicateName_summary_cantTell: "This table's caption duplicates its summary attribute.",
1019
- tableDuplicateName_hint_cantTell:
1020
- 'Remove the redundant summary attribute, or make it provide different information than the caption.',
1021
-
1022
- metaViewportLarge_title: 'Viewport meta tag should allow zooming up to 500%',
1023
- metaViewportLarge_description:
1024
- 'Checks that <meta name="viewport"> does not set user-scalable=no or maximum-scale below 5 (500%).',
1025
- metaViewportLarge_summary_cantTell:
1026
- 'This viewport meta tag restricts zoom below the 500% best-practice target.',
1027
- metaViewportLarge_hint_cantTell:
1028
- 'Remove user-scalable=no and raise maximum-scale to at least 5 (500%) if possible.',
1029
-
1030
- presentationRoleConflict_title:
1031
- 'Presentational role must not conflict with a global ARIA attribute or focusability',
1032
- presentationRoleConflict_description:
1033
- 'Checks that role="presentation"/"none" (including an <img alt=""> implicit presentation role) is not combined with a global ARIA attribute (aria-label, aria-hidden, aria-describedby, ...) or focusability (tabindex/native).',
1034
- presentationRoleConflict_summary_cantTell:
1035
- 'This role="{{role}}" element also has a conflicting condition ({{attrs}}), which restores its implicit role and cancels the presentational intent.',
1036
- presentationRoleConflict_hint_cantTell:
1037
- 'Remove the conflicting naming attribute(s) and/or focusability (tabindex/native) if the element should stay presentational, or remove role="presentation"/"none" if it should be exposed to assistive technology.',
1038
-
1039
- region_title: 'Page content should be inside a landmark region',
1040
- region_description: 'Checks that content under <body> is contained within a landmark region.',
1041
- region_summary_cantTell: 'This content is not contained within a landmark region.',
1042
- region_hint_cantTell:
1043
- 'Move this content inside a landmark region (main, nav, aside, a labeled section, etc.).',
1044
-
1045
- skipLink_title: 'Skip link must have a resolvable, usable target',
1046
- skipLink_description:
1047
- 'Checks that a "skip to ..." link\'s href fragment resolves to a real, currently usable element in the document.',
1048
- skipLink_summary_cantTell: "This skip link's target does not exist.",
1049
- skipLink_hint_cantTell:
1050
- "Point the skip link's href at an id that exists in the document, or add the missing target element.",
1051
- skipLink_summary_unusableTarget_cantTell:
1052
- 'This skip link points to a target that exists but is not currently usable.',
1053
- skipLink_hint_unusableTarget_cantTell:
1054
- 'Point this skip link to a target that is exposed and usable as a navigation destination.',
1055
-
1056
- autocompleteValid_title: 'autocomplete attribute must be a valid autofill value',
1057
- autocompleteValid_description:
1058
- 'Checks that a non-empty autocomplete attribute is "on"/"off" or a well-formed autofill detail token list.',
1059
- autocompleteValid_summary_fail:
1060
- 'This autocomplete attribute value is not a valid autofill value.',
1061
- autocompleteValid_hint_fail:
1062
- 'Use "on"/"off", or a valid autofill token list (e.g. "shipping street-address", "cc-number").',
1063
-
1064
- htmlXmlLangMismatch_title: 'lang and xml:lang must not disagree',
1065
- htmlXmlLangMismatch_description:
1066
- "Checks that the <html> element's lang and xml:lang attributes declare the same primary language, when both are present.",
1067
- htmlXmlLangMismatch_summary_fail:
1068
- 'The lang ("{{lang}}") and xml:lang ("{{xmlLang}}") attributes declare different languages.',
1069
- htmlXmlLangMismatch_hint_fail:
1070
- 'Make lang and xml:lang declare the same primary language, or remove the deprecated xml:lang attribute.',
1071
-
1072
- avoidInlineSpacing_title: 'Inline style must not force text spacing with !important',
1073
- avoidInlineSpacing_description:
1074
- 'Checks that inline style does not set line-height, letter-spacing, or word-spacing with !important, which blocks user text-spacing overrides.',
1075
- avoidInlineSpacing_summary_fail:
1076
- "This element's inline style forces {{properties}} with !important, blocking user text-spacing overrides.",
1077
- avoidInlineSpacing_hint_fail:
1078
- 'Remove !important from line-height/letter-spacing/word-spacing in inline styles so users can override text spacing.',
1079
-
1080
- metaRefreshNoExceptions_title: 'Page must not use a meta refresh at all (AAA)',
1081
- metaRefreshNoExceptions_description:
1082
- 'Checks that <meta http-equiv="refresh"> is not present at all, regardless of delay — the stricter AAA-level counterpart of the A-level positive-delay-only check.',
1083
- metaRefreshNoExceptions_summary_fail:
1084
- 'This page uses a meta refresh, which is an automatic context change not initiated by the user.',
1085
- metaRefreshNoExceptions_hint_fail:
1086
- 'Remove the meta refresh; trigger the redirect/refresh only in response to a user action instead.',
1087
-
1088
- validLang_title: 'Element lang attribute must be syntactically valid',
1089
- validLang_description:
1090
- 'Checks that any element (other than the root <html>) with a non-empty lang attribute uses a syntactically valid language tag.',
1091
- validLang_summary_fail:
1092
- 'This lang attribute value ("{{value}}") is not a syntactically valid language tag.',
1093
- validLang_hint_fail: 'Use a valid BCP47 language tag (e.g. "fr", "es-MX").',
1094
-
1095
- linkInTextBlock_title:
1096
- 'Links in text blocks must be distinguishable from surrounding text without relying on color alone',
1097
- linkInTextBlock_description:
1098
- 'Checks that a link inside a run of text is visually distinguishable from the surrounding text by underline, a font-weight/style difference, or a sufficient (>=3:1) color-contrast difference — not by color alone.',
1099
- linkInTextBlock_summary_fail:
1100
- 'This link in a block of text relies on color alone to be distinguished from the surrounding text.',
1101
- linkInTextBlock_hint_fail:
1102
- 'Add an underline, a font-weight/style difference, or increase the color contrast between the link and surrounding text to at least 3:1.',
1103
-
1104
- noAutoplayAudio_title:
1105
- 'Autoplaying audio should provide a pause/stop or volume-control mechanism',
1106
- noAutoplayAudio_description:
1107
- 'Flags <audio>/<video> elements that autoplay unmuted with no native controls attribute, for manual review against the 3-second exemption in WCAG 1.4.2.',
1108
- noAutoplayAudio_summary_cantTell:
1109
- 'This element autoplays audio without a native pause/stop or volume-control mechanism.',
1110
- noAutoplayAudio_hint_cantTell:
1111
- 'If this clip plays for more than 3 seconds, add a controls attribute (or an equivalent custom mechanism) so users can pause/stop it or control its volume independently of the system volume.',
1112
-
1113
- videoCaption_title: 'Prerecorded video should provide a captions track',
1114
- videoCaption_description:
1115
- 'Flags <video> elements with no <track kind="captions"|"subtitles"> child, for manual review of whether the video has an audio track that needs captions.',
1116
- videoCaption_summary_cantTell: 'This video has no captions (or subtitles) track.',
1117
- videoCaption_hint_cantTell:
1118
- 'If this video has an audio track that conveys information, add a <track kind="captions" src="..."> with the captioned content.',
1119
-
1120
- scrollableRegionFocusable_title:
1121
- 'Scrollable regions with no focusable content should be keyboard-focusable',
1122
- scrollableRegionFocusable_description:
1123
- 'Flags elements whose CSS declares overflow:auto/scroll, contain no focusable descendant, and are not themselves keyboard-focusable, for manual review of whether their content actually overflows and needs keyboard scroll access.',
1124
- scrollableRegionFocusable_summary_cantTell:
1125
- 'This element declares overflow:auto/scroll, has no focusable descendant, and is not itself keyboard-focusable.',
1126
- scrollableRegionFocusable_hint_cantTell:
1127
- 'If this region\'s content actually overflows, add tabindex="0" (and a suitable label) so keyboard users can focus it and scroll with the arrow keys.',
1128
-
1129
- identicalLinksSamePurpose_title:
1130
- 'Links with the same accessible name should lead to the same destination',
1131
- identicalLinksSamePurpose_description:
1132
- 'Flags groups of links that share the same accessible name but resolve to more than one distinct destination, for manual review of whether they serve the same purpose.',
1133
- identicalLinksSamePurpose_summary_cantTell:
1134
- 'This link shares an accessible name with other links on the page that lead to a different destination.',
1135
- identicalLinksSamePurpose_hint_cantTell:
1136
- 'Ensure links with the same text serve the same purpose, or make the link text distinct enough to describe each destination.',
1137
-
1138
- ariaBrailleEquivalent_title:
1139
- 'aria-braillelabel/aria-brailleroledescription must have a non-braille equivalent',
1140
- ariaBrailleEquivalent_description:
1141
- 'Checks that elements using aria-braillelabel also have a regular accessible name, and elements using aria-brailleroledescription also have aria-roledescription.',
1142
- ariaBrailleEquivalent_summary_fail:
1143
- 'This element has {{attr}} but no {{requires}}, its non-braille equivalent.',
1144
- ariaBrailleEquivalent_hint_fail:
1145
- '{{attr}} is a Braille-specific supplement, not a replacement — also provide {{requires}}.',
1146
-
1147
- ariaConditionalAttr_title:
1148
- 'aria-errormessage requires aria-invalid to be set to a non-false value',
1149
- ariaConditionalAttr_description:
1150
- 'Checks that elements with aria-errormessage also have aria-invalid set to "true", "grammar", or "spelling" — otherwise the error message is dropped from the accessibility tree.',
1151
- ariaConditionalAttr_summary_fail:
1152
- 'This element has aria-errormessage but aria-invalid is missing or "false", so the error message is not exposed.',
1153
- ariaConditionalAttr_hint_fail:
1154
- 'Set aria-invalid to "true" (or "grammar"/"spelling") whenever aria-errormessage should be exposed to assistive technology.',
1155
-
1156
- ariaCheckedStateMismatch_title:
1157
- 'Native checkbox/radio aria-checked should match its actual state',
1158
- ariaCheckedStateMismatch_description:
1159
- 'Flags a native <input type="checkbox">/<input type="radio"> whose explicit aria-checked value disagrees with its actual checked/indeterminate state, for manual review.',
1160
- ariaCheckedStateMismatch_summary_cantTell:
1161
- 'This element’s aria-checked value does not match its actual checked/indeterminate state.',
1162
- ariaCheckedStateMismatch_hint_cantTell:
1163
- 'Set aria-checked to match the element’s real state, or remove it — a native checkbox/radio already exposes this state without it.',
1164
-
1165
- cssOrientationLock_title: 'CSS must not lock the page to a single orientation',
1166
- cssOrientationLock_description:
1167
- 'Checks that no @media (orientation: portrait|landscape) rule sets a transform: rotate(...) on the page, a known technique for defeating device orientation.',
1168
- cssOrientationLock_summary_fail:
1169
- 'A "{{mediaText}}" media query rotates "{{selectorText}}", locking the page to one orientation.',
1170
- cssOrientationLock_hint_fail:
1171
- 'Remove the rotate() transform from the orientation media query; let the page respond naturally to device orientation instead of forcing a visual rotation.',
1172
-
1173
- ariaText_title: 'role="text" elements should have no focusable descendants',
1174
- ariaText_description:
1175
- 'Checks that elements with role="text" contain no focusable descendant (link, button, form control, tabindex, iframe, or contenteditable).',
1176
- ariaText_summary_cantTell: 'This role="text" element contains a focusable descendant.',
1177
- ariaText_hint_cantTell:
1178
- 'Remove role="text" (or remove the focusable descendant) — a "plain text" region should not contain focusable content.',
1179
-
1180
- focusOrderSemantics_title: 'Elements added to the tab order should have interactive semantics',
1181
- focusOrderSemantics_description:
1182
- 'Flags elements with tabindex >= 0 whose explicit role is a non-interactive structural/document role (e.g. heading, list, region, presentation), for manual review.',
1183
- focusOrderSemantics_summary_cantTell:
1184
- 'This element is in the tab order (tabindex="{{tabindex}}") but has a non-interactive role ("{{role}}").',
1185
- focusOrderSemantics_hint_cantTell:
1186
- 'Remove tabindex if this element is not meant to be interactive, or use an interactive role that matches its actual behavior.',
1187
-
1188
- pAsHeading_title: 'A <p> styled to look like a heading should probably be a real heading',
1189
- pAsHeading_description:
1190
- 'Flags short <p> elements whose entire text is bold and rendered at >=18px, for manual review of whether a real heading element should be used instead.',
1191
- pAsHeading_summary_cantTell:
1192
- 'This paragraph is entirely bold and rendered at a heading-like size.',
1193
- pAsHeading_hint_cantTell:
1194
- 'If this text introduces a new section, use a real heading element (<h1>-<h6> or role="heading") instead of styling a paragraph to look like one.',
1195
-
1196
- tableFakeCaption_title: "A table's first row should not stand in for a real <caption>",
1197
- tableFakeCaption_description:
1198
- 'Flags tables with no <caption> whose first row has a single non-empty cell while other rows have multiple cells, for manual review of whether that cell is acting as a fake caption.',
1199
- tableFakeCaption_summary_cantTell:
1200
- 'This table has no <caption>, but its first row is a single cell sitting above multi-cell rows — it may be acting as a fake caption.',
1201
- tableFakeCaption_hint_cantTell:
1202
- 'If this cell is meant to describe the table, use a real <caption> element instead of a lone first-row cell.',
1203
-
1204
- tdHasHeader_title: 'Data cells in large tables must have an associated header',
1205
- tdHasHeader_description:
1206
- 'Checks that every <td> in a large, simple (no colspan/rowspan) table has an associated header — via a headers attribute, an implicit column <th> above it, or an implicit row <th> to its left.',
1207
- tdHasHeader_summary_fail:
1208
- 'This data cell has no associated header (no headers attribute, no column <th> above it, no row <th> to its left).',
1209
- tdHasHeader_hint_fail:
1210
- 'Add a headers attribute referencing the relevant <th> id(s), or restructure the table so this cell has an implicit row/column header.',
1211
-
1212
- mouseOnlyEventHandlers_title:
1213
- 'Pointer-only inline event handlers should have a keyboard-reachable equivalent',
1214
- mouseOnlyEventHandlers_description:
1215
- 'Flags elements with an inline pointer-only event handler (onmouseover, onmouseout, onmousedown, onmouseup, ondblclick, onmousemove, onmouseenter, onmouseleave) and no keyboard-reachable equivalent (onkeydown/onkeyup/onkeypress/onfocus/onblur), for manual review.',
1216
- mouseOnlyEventHandlers_summary_cantTell:
1217
- 'This element has {{attrs}} but no keyboard-reachable equivalent handler.',
1218
- mouseOnlyEventHandlers_hint_cantTell:
1219
- 'Add onkeydown/onkeyup/onkeypress (or onfocus/onblur for hover-triggered behavior) so this functionality is also reachable by keyboard.',
1220
-
1221
- linkNameQuality_title: 'Link text should be descriptive, not generic',
1222
- linkNameQuality_description:
1223
- 'Flags links whose full accessible name is a known non-descriptive phrase (e.g. "click here", "read more", "more"), for manual review of whether the purpose is clear without additional context.',
1224
- linkNameQuality_summary_cantTell:
1225
- 'This link\'s accessible name ("{{name}}") is a generic, non-descriptive phrase.',
1226
- linkNameQuality_hint_cantTell:
1227
- 'Make the link text itself describe its destination/purpose (e.g. "Download the 2026 pricing guide" instead of "Download"), or confirm the surrounding context already makes the purpose clear.'
1228
- };