@surea11y/core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/LICENSE +21 -0
- package/README.md +145 -0
- package/bin/core.js +244 -0
- package/docs/BINDING_AUTHORS_GUIDE.md +41 -0
- package/docs/CLI.md +49 -0
- package/docs/ENGINE_OPTIONS.md +155 -0
- package/docs/I18N.md +47 -0
- package/docs/INTEGRATION.md +156 -0
- package/docs/LIMITATIONS.md +31 -0
- package/docs/OUTPUT_SCHEMA.md +237 -0
- package/docs/POLICY.md +71 -0
- package/docs/RULE_AUTHORING.md +375 -0
- package/docs/RULE_CATALOG.md +180 -0
- package/docs/RULE_TAXONOMY.md +145 -0
- package/docs/TROUBLESHOOTING.md +48 -0
- package/docs/WCAG_CONFORMANCE.md +49 -0
- package/package.json +60 -0
- package/src/catalogs/composites.wcag.js +490 -0
- package/src/checks/automatic/area-alt-present.js +225 -0
- package/src/checks/automatic/aria-allowed-attr.js +206 -0
- package/src/checks/automatic/aria-allowed-role.js +102 -0
- package/src/checks/automatic/aria-braille-equivalent.js +139 -0
- package/src/checks/automatic/aria-conditional-attr.js +110 -0
- package/src/checks/automatic/aria-deprecated-role.js +106 -0
- package/src/checks/automatic/aria-hidden-body.js +87 -0
- package/src/checks/automatic/aria-hidden-focus.js +480 -0
- package/src/checks/automatic/aria-prohibited-attr.js +156 -0
- package/src/checks/automatic/aria-prohibited-children.js +265 -0
- package/src/checks/automatic/aria-required-attr.js +154 -0
- package/src/checks/automatic/aria-required-children.js +274 -0
- package/src/checks/automatic/aria-required-parent.js +222 -0
- package/src/checks/automatic/aria-role-name-present.js +201 -0
- package/src/checks/automatic/aria-roles-valid.js +110 -0
- package/src/checks/automatic/aria-valid-attr-value.js +123 -0
- package/src/checks/automatic/aria-valid-attr.js +109 -0
- package/src/checks/automatic/autocomplete-valid.js +134 -0
- package/src/checks/automatic/avoid-inline-spacing.js +107 -0
- package/src/checks/automatic/binary-control-name-present.js +294 -0
- package/src/checks/automatic/button-name-present.js +146 -0
- package/src/checks/automatic/bypass-blocks-present.js +162 -0
- package/src/checks/automatic/canvas-text-alternative-present.js +140 -0
- package/src/checks/automatic/combobox-name-present.js +267 -0
- package/src/checks/automatic/contrast-computable.js +378 -0
- package/src/checks/automatic/contrast-enhanced.js +517 -0
- package/src/checks/automatic/contrast-minimum.js +512 -0
- package/src/checks/automatic/css-orientation-lock.js +206 -0
- package/src/checks/automatic/definition-list-children-valid.js +148 -0
- package/src/checks/automatic/deprecated-elements-not-used.js +91 -0
- package/src/checks/automatic/dialog-name-present.js +209 -0
- package/src/checks/automatic/dlitem-parent-valid.js +100 -0
- package/src/checks/automatic/duplicate-id-aria.js +126 -0
- package/src/checks/automatic/embed-text-alternative-present.js +190 -0
- package/src/checks/automatic/form-control-programmatic-label-present.js +409 -0
- package/src/checks/automatic/form-control-single-label.js +117 -0
- package/src/checks/automatic/html-xml-lang-mismatch.js +91 -0
- package/src/checks/automatic/iframe-focusable-content.js +141 -0
- package/src/checks/automatic/iframe-name-present.js +102 -0
- package/src/checks/automatic/iframe-title-unique.js +107 -0
- package/src/checks/automatic/img-alt-present.js +223 -0
- package/src/checks/automatic/input-image-alt-present.js +155 -0
- package/src/checks/automatic/label-in-name.js +326 -0
- package/src/checks/automatic/language-page-present.js +159 -0
- package/src/checks/automatic/link-in-text-block.js +218 -0
- package/src/checks/automatic/link-name-present.js +114 -0
- package/src/checks/automatic/list-children-valid.js +152 -0
- package/src/checks/automatic/listbox-name-present.js +236 -0
- package/src/checks/automatic/listitem-parent-valid.js +118 -0
- package/src/checks/automatic/menuitem-name-present.js +201 -0
- package/src/checks/automatic/meta-refresh-no-exceptions.js +105 -0
- package/src/checks/automatic/meta-refresh-timing-absent.js +107 -0
- package/src/checks/automatic/meta-viewport-zoom-enabled.js +118 -0
- package/src/checks/automatic/meter-name-present.js +160 -0
- package/src/checks/automatic/nested-interactive-controls-absent.js +135 -0
- package/src/checks/automatic/object-text-alternative-present.js +193 -0
- package/src/checks/automatic/option-name-present.js +157 -0
- package/src/checks/automatic/page-title-present.js +86 -0
- package/src/checks/automatic/progressbar-name-present.js +165 -0
- package/src/checks/automatic/role-img-alt-present.js +206 -0
- package/src/checks/automatic/searchbox-name-present.js +236 -0
- package/src/checks/automatic/server-side-image-map-absent.js +88 -0
- package/src/checks/automatic/slider-name-present.js +276 -0
- package/src/checks/automatic/spinbutton-name-present.js +236 -0
- package/src/checks/automatic/summary-name-present.js +153 -0
- package/src/checks/automatic/svg-image-text-alternative-present.js +220 -0
- package/src/checks/automatic/svg-text-alternative-present.js +298 -0
- package/src/checks/automatic/tab-name-present.js +200 -0
- package/src/checks/automatic/table-headers-attr-valid.js +122 -0
- package/src/checks/automatic/table-th-has-data-cells.js +117 -0
- package/src/checks/automatic/target-size-minimum.js +605 -0
- package/src/checks/automatic/td-has-header.js +151 -0
- package/src/checks/automatic/textbox-name-present.js +236 -0
- package/src/checks/automatic/tooltip-name-present.js +158 -0
- package/src/checks/automatic/treeitem-name-present.js +157 -0
- package/src/checks/automatic/valid-lang.js +100 -0
- package/src/checks/automatic/video-poster-text-alternative-present.js +193 -0
- package/src/checks/manual/accesskeys-manual.js +93 -0
- package/src/checks/manual/area-alt-decorative-manual.js +247 -0
- package/src/checks/manual/area-alt-quality-manual.js +204 -0
- package/src/checks/manual/aria-checked-state-mismatch-manual.js +141 -0
- package/src/checks/manual/aria-text-manual.js +109 -0
- package/src/checks/manual/canvas-text-alternative-quality-manual.js +170 -0
- package/src/checks/manual/css-hidden-focus.js +259 -0
- package/src/checks/manual/embed-text-alternative-quality-manual.js +204 -0
- package/src/checks/manual/empty-heading-manual.js +182 -0
- package/src/checks/manual/empty-table-header-manual.js +163 -0
- package/src/checks/manual/focus-order-semantics-manual.js +117 -0
- package/src/checks/manual/form-control-programmatic-label-quality-manual.js +291 -0
- package/src/checks/manual/heading-order-manual.js +130 -0
- package/src/checks/manual/identical-links-same-purpose-manual.js +142 -0
- package/src/checks/manual/image-redundant-alt-manual.js +118 -0
- package/src/checks/manual/img-alt-decorative-manual.js +148 -0
- package/src/checks/manual/img-alt-quality-manual.js +182 -0
- package/src/checks/manual/input-image-alt-decorative-manual.js +144 -0
- package/src/checks/manual/input-image-alt-quality-manual.js +144 -0
- package/src/checks/manual/label-title-only-manual.js +115 -0
- package/src/checks/manual/landmark-banner-is-top-level-manual.js +180 -0
- package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +169 -0
- package/src/checks/manual/landmark-main-is-top-level-manual.js +167 -0
- package/src/checks/manual/landmark-no-duplicate-banner-manual.js +177 -0
- package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +169 -0
- package/src/checks/manual/landmark-no-duplicate-main-manual.js +132 -0
- package/src/checks/manual/landmark-one-main-manual.js +151 -0
- package/src/checks/manual/landmark-unique-manual.js +252 -0
- package/src/checks/manual/link-name-quality-manual.js +143 -0
- package/src/checks/manual/media-transcript-present-manual.js +373 -0
- package/src/checks/manual/meta-viewport-large-manual.js +119 -0
- package/src/checks/manual/mouse-only-event-handlers-manual.js +134 -0
- package/src/checks/manual/no-autoplay-audio-manual.js +116 -0
- package/src/checks/manual/object-text-alternative-quality-manual.js +194 -0
- package/src/checks/manual/p-as-heading-manual.js +163 -0
- package/src/checks/manual/page-has-heading-one-manual.js +110 -0
- package/src/checks/manual/page-title-patterns-manual.js +262 -0
- package/src/checks/manual/presentation-role-conflict-manual.js +159 -0
- package/src/checks/manual/region-manual.js +183 -0
- package/src/checks/manual/scope-attr-valid-manual.js +93 -0
- package/src/checks/manual/scrollable-region-focusable-manual.js +168 -0
- package/src/checks/manual/skip-link-manual.js +150 -0
- package/src/checks/manual/svg-text-alternative-quality-manual.js +209 -0
- package/src/checks/manual/tabindex-manual.js +94 -0
- package/src/checks/manual/table-duplicate-name-manual.js +99 -0
- package/src/checks/manual/table-fake-caption-manual.js +122 -0
- package/src/checks/manual/video-caption-manual.js +118 -0
- package/src/checks/manual-review.js +95 -0
- package/src/checks/rules-and-tags.full.csv +19 -0
- package/src/checks/rules-and-tags.full.json +259 -0
- package/src/core/aria-helpers.js +906 -0
- package/src/core/contrast-helpers.js +1147 -0
- package/src/core/dom-helpers.js +4085 -0
- package/src/core/dom-runner.js +627 -0
- package/src/core/frame-messaging.js +210 -0
- package/src/core/frame-scan.js +178 -0
- package/src/core/rollup-composites.js +135 -0
- package/src/core/rule-meta.js +140 -0
- package/src/core.js +79055 -0
- package/src/coverage/wcag-facets.js +1079 -0
- package/src/coverage/wcag-version-map.js +84 -0
- package/src/i18n/en.js +919 -0
- package/src/i18n/fr.js +527 -0
- package/src/index.js +4 -0
- package/src/policy/contracts.js +18 -0
- package/src/policy/resolvePolicy.js +55 -0
- package/src/policy/schemas/engine-options.schema.json +103 -0
- package/src/policy/schemas/policy-contract.schema.json +40 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Rule catalog
|
|
2
|
+
|
|
3
|
+
Generated from the compiled engine's own catalog (`getChecksCatalog()`/`getRulesCatalog()`) — run `node scripts/generate-rule-catalog.js` after `npm run build` to regenerate this file whenever rules change. Do not hand-edit.
|
|
4
|
+
|
|
5
|
+
**125 rules total: 77 automatic (WCAG-normative, can return `fail`), 48 manual (advisory/judgment-required, capped at `cantTell`). 101 carry at least one formal WCAG Success Criterion mapping.**
|
|
6
|
+
|
|
7
|
+
See [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md) for what `type`/`confidence`/`severity` mean on a scan result, and [`WCAG_CONFORMANCE.md`](./WCAG_CONFORMANCE.md) for how these roll up to an SC-level conformance claim. For WCAG-facet-level coverage-gap tracking (which parts of an SC are and aren't automatable yet), see `coverage/coverage-report.md` instead — that one is organized by facet, this one by rule.
|
|
8
|
+
|
|
9
|
+
## Automatic rules (77) — can return `fail`
|
|
10
|
+
|
|
11
|
+
| Rule ID | Title | WCAG SC | Level | Confidence | Default severity |
|
|
12
|
+
|---|---|---|---|---|---|
|
|
13
|
+
| `area-alt-present` | <area> must have an alt attribute | 1.1.1 | A | high | serious |
|
|
14
|
+
| `aria-allowed-attr` | aria-* attributes must be permitted for the element’s role | 4.1.2 | A | medium | moderate |
|
|
15
|
+
| `aria-allowed-role` | Explicit role must be permitted for its host element | 4.1.2 | A | high | moderate |
|
|
16
|
+
| `aria-braille-equivalent` | aria-braillelabel/aria-brailleroledescription must have a non-braille equivalent | 4.1.2 | A | high | serious |
|
|
17
|
+
| `aria-conditional-attr` | aria-errormessage requires aria-invalid to be set to a non-false value | 4.1.2 | A | high | serious |
|
|
18
|
+
| `aria-deprecated-role` | role attribute must not use a deprecated or author-prohibited ARIA role | 4.1.2 | A | high | moderate |
|
|
19
|
+
| `aria-hidden-body` | The document <body> must not be aria-hidden | 1.3.1, 4.1.2 | A | high | critical |
|
|
20
|
+
| `aria-hidden-focus` | ARIA hidden elements must not be focusable | 2.4.7, 4.1.2 | AA | high | serious |
|
|
21
|
+
| `aria-prohibited-attr` | ARIA naming attributes must not be used on roles that prohibit them | 4.1.2 | A | high | moderate |
|
|
22
|
+
| `aria-prohibited-children` | Container roles must not own a child with a disallowed role | 4.1.2 | A | medium | moderate |
|
|
23
|
+
| `aria-required-attr` | Roles with a required ARIA state/property must carry it | 4.1.2 | A | high | serious |
|
|
24
|
+
| `aria-required-children` | Container roles must own at least one required child role | 4.1.2 | A | medium | moderate |
|
|
25
|
+
| `aria-required-parent` | Roles requiring a specific context role must be in that context | 4.1.2 | A | medium | moderate |
|
|
26
|
+
| `aria-role-name-present` | ARIA widget/container roles have an accessible name | 4.1.2 | A | high | serious |
|
|
27
|
+
| `aria-roles-valid` | role attribute must be a valid, non-abstract ARIA role | 4.1.2 | A | high | serious |
|
|
28
|
+
| `aria-valid-attr` | aria-* attributes must be real, defined ARIA attributes | 4.1.2 | A | high | serious |
|
|
29
|
+
| `aria-valid-attr-value` | aria-* attribute values must match their declared type | 4.1.2 | A | high | serious |
|
|
30
|
+
| `autocomplete-valid` | autocomplete attribute must be a valid autofill value | 1.3.5 | AA | high | moderate |
|
|
31
|
+
| `avoid-inline-spacing` | Inline style must not force text spacing with !important | 1.4.12 | AA | high | moderate |
|
|
32
|
+
| `binary-control-name-present` | Binary controls have an accessible name | 4.1.2 | A | high | serious |
|
|
33
|
+
| `button-name-present` | Buttons have an accessible name | 4.1.2 | A | high | serious |
|
|
34
|
+
| `bypass-blocks-present` | Page must provide a way to bypass repeated blocks | 2.4.1 | A | medium | serious |
|
|
35
|
+
| `canvas-text-alternative-present` | <canvas> must provide a text alternative | 1.1.1 | A | high | serious |
|
|
36
|
+
| `combobox-name-present` | Comboboxes have an accessible name | 4.1.2 | A | high | serious |
|
|
37
|
+
| `contrast-computable` | Color contrast is computable for rendered text | 1.4.3, 1.4.6 | AAA | high | serious |
|
|
38
|
+
| `contrast-enhanced` | Text meets enhanced color contrast (AAA) | 1.4.6 | AAA | high | serious |
|
|
39
|
+
| `contrast-minimum` | Text meets minimum color contrast (AA) | 1.4.3 | AA | high | serious |
|
|
40
|
+
| `css-orientation-lock` | CSS must not lock the page to a single orientation | 1.3.4 | AA | high | serious |
|
|
41
|
+
| `definition-list-children-valid` | Description lists must be structured correctly | 1.3.1 | A | high | serious |
|
|
42
|
+
| `deprecated-elements-not-used` | Obsolete non-stoppable elements (<blink>, <marquee>) must not be used | 2.2.2 | A | high | serious |
|
|
43
|
+
| `dialog-name-present` | Dialogs have an accessible name | 4.1.2 | A | high | serious |
|
|
44
|
+
| `dlitem-parent-valid` | Description-list items must be inside a description list | 1.3.1 | A | high | serious |
|
|
45
|
+
| `duplicate-id-aria` | IDs referenced by ARIA must be unique | 4.1.2 | A | high | serious |
|
|
46
|
+
| `embed-text-alternative-present` | <embed> must provide a text alternative | 1.1.1 | A | high | serious |
|
|
47
|
+
| `form-control-programmatic-label-present` | Form controls must have a programmatic label | 1.3.1, 3.3.2, 4.1.2 | A | medium | serious |
|
|
48
|
+
| `form-control-single-label` | Form controls must not have multiple labels | 3.3.2 | A | high | moderate |
|
|
49
|
+
| `html-lang-attr-present` | Page language is declared | 3.1.1 | A | high | serious |
|
|
50
|
+
| `html-xml-lang-mismatch` | lang and xml:lang must not disagree | 3.1.1 | A | high | serious |
|
|
51
|
+
| `iframe-focusable-content` | Frames with tabindex="-1" must not contain focusable content | 2.1.1 | A | high | moderate |
|
|
52
|
+
| `iframe-name-present` | Frames have an accessible name | 4.1.2 | A | high | serious |
|
|
53
|
+
| `iframe-title-unique` | Frame titles must be unique | 4.1.2 | A | high | moderate |
|
|
54
|
+
| `img-alt-present` | <img> must have an alt attribute | 1.1.1 | A | high | serious |
|
|
55
|
+
| `input-image-alt-present` | <input type="image"> must have an alt attribute | 1.1.1 | A | high | serious |
|
|
56
|
+
| `label-in-name` | Label in Name: accessible name contains visible text | 2.5.3 | A | high | serious |
|
|
57
|
+
| `link-in-text-block` | Links in text blocks must be distinguishable from surrounding text without relying on color alone | 1.4.1 | A | high | serious |
|
|
58
|
+
| `link-name-present` | Links have an accessible name | 4.1.2 | A | high | serious |
|
|
59
|
+
| `list-children-valid` | Lists must only directly contain list items | 1.3.1 | A | high | serious |
|
|
60
|
+
| `listbox-name-present` | Listboxes have an accessible name | 4.1.2 | A | high | serious |
|
|
61
|
+
| `listitem-parent-valid` | List items must be inside a list container | 1.3.1 | A | high | serious |
|
|
62
|
+
| `menuitem-name-present` | Menu items have an accessible name | 4.1.2 | A | high | serious |
|
|
63
|
+
| `meta-refresh-no-exceptions` | Page must not use a meta refresh at all (AAA) | 2.2.4, 3.2.5 | AAA | high | moderate |
|
|
64
|
+
| `meta-refresh-timing-absent` | Page must not use a timed meta refresh | 2.2.1 | A | high | serious |
|
|
65
|
+
| `meta-viewport-zoom-enabled` | Viewport meta tag must not disable zoom | 1.4.4 | AA | high | serious |
|
|
66
|
+
| `meter-name-present` | Meters have an accessible name | 1.1.1 | A | high | serious |
|
|
67
|
+
| `nested-interactive-controls-absent` | Interactive controls must not be nested | 4.1.2 | A | high | serious |
|
|
68
|
+
| `object-text-alternative-present` | <object> must provide a text alternative | 1.1.1 | A | high | serious |
|
|
69
|
+
| `option-name-present` | Options have an accessible name | 4.1.2 | A | high | serious |
|
|
70
|
+
| `page-title-present` | Page has a non-empty title | 2.4.2 | A | high | serious |
|
|
71
|
+
| `progressbar-name-present` | Progress bars have an accessible name | 1.1.1 | A | high | serious |
|
|
72
|
+
| `role-img-text-alternative-present` | [role="img"] must have an accessible text alternative | 1.1.1 | A | high | serious |
|
|
73
|
+
| `searchbox-name-present` | Searchboxes have an accessible name | 4.1.2 | A | high | serious |
|
|
74
|
+
| `server-side-image-map-absent` | Images must not use a server-side image map | 2.1.1 | A | high | serious |
|
|
75
|
+
| `slider-name-present` | Sliders have an accessible name | 4.1.2 | A | high | serious |
|
|
76
|
+
| `spinbutton-name-present` | Spinbuttons have an accessible name | 4.1.2 | A | high | serious |
|
|
77
|
+
| `summary-name-present` | Summary elements have an accessible name | 4.1.2 | A | high | serious |
|
|
78
|
+
| `svg-image-text-alternative-present` | SVG <image> must have a text alternative | 1.1.1 | A | medium | serious |
|
|
79
|
+
| `svg-text-alternative-present` | <svg> must provide a text alternative | 1.1.1 | A | high | serious |
|
|
80
|
+
| `tab-name-present` | Tabs have an accessible name | 4.1.2 | A | high | serious |
|
|
81
|
+
| `table-headers-attr-valid` | Table cell "headers" attribute must reference valid header cells | 1.3.1 | A | high | serious |
|
|
82
|
+
| `table-th-has-data-cells` | <th> elements must describe at least one data cell | 1.3.1 | A | high | moderate |
|
|
83
|
+
| `target-size-minimum` | Pointer targets must be at least 24x24px large, or leave sufficient distance to other targets | 2.5.8 | AA | medium | serious |
|
|
84
|
+
| `td-has-header` | Data cells in large tables must have an associated header | 1.3.1 | A | high | serious |
|
|
85
|
+
| `textbox-name-present` | Textboxes have an accessible name | 4.1.2 | A | high | serious |
|
|
86
|
+
| `tooltip-name-present` | Tooltips have an accessible name | 4.1.2 | A | high | serious |
|
|
87
|
+
| `treeitem-name-present` | Tree items have an accessible name | 4.1.2 | A | high | serious |
|
|
88
|
+
| `valid-lang` | Element lang attribute must be syntactically valid | 3.1.2 | AA | high | moderate |
|
|
89
|
+
| `video-poster-text-alternative-present` | <video> poster must have a text alternative | 1.1.1 | A | medium | serious |
|
|
90
|
+
|
|
91
|
+
## Manual rules (48) — advisory, capped at `cantTell`
|
|
92
|
+
|
|
93
|
+
| Rule ID | Title | WCAG SC | Level | Confidence | Default severity |
|
|
94
|
+
|---|---|---|---|---|---|
|
|
95
|
+
| `accesskeys` | accesskey values must be unique | — | — | medium | minor |
|
|
96
|
+
| `area-alt-decorative` | <area> with alt="" must be decorative (manual review) | 1.1.1 | A | medium | minor |
|
|
97
|
+
| `area-alt-quality` | <area> alt text must be appropriate (manual review) | 1.1.1 | A | medium | minor |
|
|
98
|
+
| `aria-checked-state-mismatch` | Native checkbox/radio aria-checked should match its actual state | 4.1.2 | A | medium | moderate |
|
|
99
|
+
| `aria-text` | role="text" elements should have no focusable descendants | — | — | medium | minor |
|
|
100
|
+
| `canvas-text-alternative-quality` | <canvas> text alternative must be appropriate (manual review) | 1.1.1 | A | medium | minor |
|
|
101
|
+
| `css-hidden-focus` | Focusable elements must not be visually hidden | 2.4.7 | AA | low | serious |
|
|
102
|
+
| `embed-text-alternative-quality` | <embed> text alternative must be appropriate (manual review) | 1.1.1 | A | medium | minor |
|
|
103
|
+
| `empty-heading` | Headings must not be empty | — | — | medium | minor |
|
|
104
|
+
| `empty-table-header` | Table header cells must not be empty | — | — | medium | minor |
|
|
105
|
+
| `focus-order-semantics` | Elements added to the tab order should have interactive semantics | — | — | medium | minor |
|
|
106
|
+
| `form-control-programmatic-label-quality` | Form controls should not rely on placeholder or title as the primary label | 4.1.2 | A | medium | moderate |
|
|
107
|
+
| `heading-order` | Heading levels must not skip a level | — | — | medium | minor |
|
|
108
|
+
| `identical-links-same-purpose` | Links with the same accessible name should lead to the same destination | 2.4.9 | AAA | low | minor |
|
|
109
|
+
| `image-redundant-alt` | Image alt text must not duplicate adjacent visible text | — | — | medium | minor |
|
|
110
|
+
| `img-alt-decorative` | <img> with alt="" must be decorative (manual review) | 1.1.1 | A | medium | minor |
|
|
111
|
+
| `img-alt-quality` | <img> alt text must be appropriate (manual review) | 1.1.1 | A | medium | minor |
|
|
112
|
+
| `input-image-alt-decorative` | <input type="image"> with alt="" must be appropriate (manual review) | 1.1.1 | A | medium | minor |
|
|
113
|
+
| `input-image-alt-quality` | <input type="image"> alt text must be appropriate (manual review) | 1.1.1 | A | medium | minor |
|
|
114
|
+
| `label-title-only` | Form controls should not use title as their only label | — | — | medium | minor |
|
|
115
|
+
| `landmark-banner-is-top-level` | Banner landmark must be top-level | — | — | medium | minor |
|
|
116
|
+
| `landmark-contentinfo-is-top-level` | Contentinfo landmark must be top-level | — | — | medium | minor |
|
|
117
|
+
| `landmark-main-is-top-level` | Main landmark must be top-level | — | — | medium | minor |
|
|
118
|
+
| `landmark-no-duplicate-banner` | Page must not have more than one banner landmark | — | — | medium | minor |
|
|
119
|
+
| `landmark-no-duplicate-contentinfo` | Page must not have more than one contentinfo landmark | — | — | medium | minor |
|
|
120
|
+
| `landmark-no-duplicate-main` | Page must not have more than one main landmark | — | — | medium | minor |
|
|
121
|
+
| `landmark-one-main` | Page should have a main landmark | — | — | medium | minor |
|
|
122
|
+
| `landmark-unique` | Landmarks with the same role must have unique names | — | — | medium | minor |
|
|
123
|
+
| `link-name-quality` | Link text should be descriptive, not generic | 2.4.4 | A | medium | minor |
|
|
124
|
+
| `manual-review` | Manual review: keyboard navigation and focus order | 2.1.1, 2.4.3, 2.4.7 | AA | medium | moderate |
|
|
125
|
+
| `media-alternative-transcript-evidence` | Time-based media: transcript or text alternative evidence | 1.2.1 | A | low | moderate |
|
|
126
|
+
| `meta-viewport-large` | Viewport meta tag should allow zooming up to 500% | — | — | medium | minor |
|
|
127
|
+
| `mouse-only-event-handlers` | Pointer-only inline event handlers should have a keyboard-reachable equivalent | 2.1.1 | A | low | moderate |
|
|
128
|
+
| `no-autoplay-audio` | Autoplaying audio should provide a pause/stop or volume-control mechanism | 1.4.2 | A | low | moderate |
|
|
129
|
+
| `object-text-alternative-quality` | <object> text alternative must be appropriate (manual review) | 1.1.1 | A | medium | minor |
|
|
130
|
+
| `p-as-heading` | A <p> styled to look like a heading should probably be a real heading | 1.3.1 | A | low | minor |
|
|
131
|
+
| `page-has-heading-one` | Page should have a level-one heading | — | — | medium | minor |
|
|
132
|
+
| `page-title-patterns` | Page title patterns that may be insufficiently descriptive | 2.4.2 | A | medium | minor |
|
|
133
|
+
| `presentation-role-conflict` | Presentational role must not conflict with a global ARIA attribute or focusability | — | — | medium | minor |
|
|
134
|
+
| `region` | Page content should be inside a landmark region | — | — | medium | minor |
|
|
135
|
+
| `scope-attr-valid` | scope attribute must have a valid value | — | — | medium | minor |
|
|
136
|
+
| `scrollable-region-focusable` | Scrollable regions with no focusable content should be keyboard-focusable | 2.1.1, 2.1.3 | AAA | low | moderate |
|
|
137
|
+
| `skip-link` | Skip link must have a resolvable target | — | — | medium | minor |
|
|
138
|
+
| `svg-text-alternative-quality` | <svg> text alternative must be appropriate (manual review) | 1.1.1 | A | medium | minor |
|
|
139
|
+
| `tabindex` | tabindex should not be greater than 0 | — | — | medium | minor |
|
|
140
|
+
| `table-duplicate-name` | Table caption must not duplicate its summary attribute | — | — | medium | minor |
|
|
141
|
+
| `table-fake-caption` | A table's first row should not stand in for a real <caption> | 1.3.1 | A | low | minor |
|
|
142
|
+
| `video-caption` | Prerecorded video should provide a captions track | 1.2.2 | A | low | moderate |
|
|
143
|
+
|
|
144
|
+
## Composite (WCAG-SC rollup) rules (31)
|
|
145
|
+
|
|
146
|
+
Composite rules aren't individually authored — they're generated rollups over the atomic rules above, one per WCAG Success Criterion with automatable coverage. See [`WCAG_CONFORMANCE.md`](./WCAG_CONFORMANCE.md) for rollup semantics.
|
|
147
|
+
|
|
148
|
+
| Composite ID | Title | WCAG SC | Level | # atomic rules rolled up |
|
|
149
|
+
|---|---|---|---|---|
|
|
150
|
+
| `wcag-1.1.1-non-text-content` | Non-text content: text alternatives | 1.1.1 | A | 22 |
|
|
151
|
+
| `wcag-1.2.1-audio-only-video-only-prerecorded` | Audio-only and video-only (prerecorded): transcript | 1.2.1 | A | 1 |
|
|
152
|
+
| `wcag-1.2.2-captions-prerecorded` | Captions (Prerecorded) | 1.2.2 | A | 1 |
|
|
153
|
+
| `wcag-1.3.1-info-and-relationships` | Info and Relationships | 1.3.1 | A | 11 |
|
|
154
|
+
| `wcag-1.3.4-orientation` | Orientation | 1.3.4 | AA | 1 |
|
|
155
|
+
| `wcag-1.3.5-identify-input-purpose` | Identify Input Purpose | 1.3.5 | AA | 1 |
|
|
156
|
+
| `wcag-1.4.1-use-of-color` | Use of Color | 1.4.1 | A | 1 |
|
|
157
|
+
| `wcag-1.4.12-text-spacing` | Text Spacing | 1.4.12 | AA | 1 |
|
|
158
|
+
| `wcag-1.4.2-audio-control` | Audio Control | 1.4.2 | A | 1 |
|
|
159
|
+
| `wcag-1.4.3-contrast-minimum` | Contrast: minimum | 1.4.3 | AA | 2 |
|
|
160
|
+
| `wcag-1.4.4-resize-text` | Resize Text | 1.4.4 | AA | 1 |
|
|
161
|
+
| `wcag-1.4.6-contrast-enhanced` | Contrast: enhanced | 1.4.6 | AAA | 2 |
|
|
162
|
+
| `wcag-2.1.1-keyboard` | Keyboard | 2.1.1 | A | 5 |
|
|
163
|
+
| `wcag-2.1.3-keyboard-no-exception` | Keyboard (No Exception) | 2.1.3 | AAA | 1 |
|
|
164
|
+
| `wcag-2.2.1-timing-adjustable` | Timing Adjustable | 2.2.1 | A | 1 |
|
|
165
|
+
| `wcag-2.2.2-pause-stop-hide` | Pause, Stop, Hide | 2.2.2 | A | 1 |
|
|
166
|
+
| `wcag-2.2.4-interruptions` | Interruptions | 2.2.4 | AAA | 1 |
|
|
167
|
+
| `wcag-2.4.1-bypass-blocks` | Bypass Blocks | 2.4.1 | A | 1 |
|
|
168
|
+
| `wcag-2.4.2-page-titled` | Page titled | 2.4.2 | A | 2 |
|
|
169
|
+
| `wcag-2.4.3-focus-order` | Focus order | 2.4.3 | A | 1 |
|
|
170
|
+
| `wcag-2.4.4-link-purpose-in-context` | Link Purpose (In Context) | 2.4.4 | A | 1 |
|
|
171
|
+
| `wcag-2.4.7-focus-visible` | Focus visible | 2.4.7 | AA | 3 |
|
|
172
|
+
| `wcag-2.4.9-link-purpose-link-only` | Link Purpose (Link Only) | 2.4.9 | AAA | 1 |
|
|
173
|
+
| `wcag-2.5.3-label-in-name` | Label in name | 2.5.3 | A | 1 |
|
|
174
|
+
| `wcag-2.5.8-target-size-minimum` | Target size: minimum | 2.5.8 | AA | 1 |
|
|
175
|
+
| `wcag-3.1.1-language-of-page` | Language of page | 3.1.1 | A | 2 |
|
|
176
|
+
| `wcag-3.1.2-language-of-parts` | Language of Parts | 3.1.2 | AA | 1 |
|
|
177
|
+
| `wcag-3.2.5-change-on-request` | Change on Request | 3.2.5 | AAA | 1 |
|
|
178
|
+
| `wcag-3.3.2-labels-or-instructions` | Labels or Instructions | 3.3.2 | A | 2 |
|
|
179
|
+
| `wcag-4.1.2-aria-validity` | Name, role, value: ARIA validity | 4.1.2 | A | 16 |
|
|
180
|
+
| `wcag-4.1.2-name` | Name, role, value: accessible name | 4.1.2 | A | 23 |
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# RULE_TAXONOMY.md — Canonical Rule Taxonomy (repo-derived)
|
|
2
|
+
|
|
3
|
+
This document describes how rules in this repository are classified.
|
|
4
|
+
It reflects **what already exists in the rules and tests**, not theory.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Primary taxonomy axes
|
|
9
|
+
|
|
10
|
+
### 1.1 Automation / Decision Kind
|
|
11
|
+
Encoded by: `meta.type`
|
|
12
|
+
|
|
13
|
+
- `automatic`
|
|
14
|
+
- Rule makes a **normative decision**
|
|
15
|
+
- Allowed outcomes: `pass`, `fail`, `notApplicable`
|
|
16
|
+
- `manual`
|
|
17
|
+
- Rule signals **human review required**
|
|
18
|
+
- Allowed outcomes: `cantTell`, `notApplicable`
|
|
19
|
+
|
|
20
|
+
Manual rules MUST NOT make normative failure decisions.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
### 1.2 Intent
|
|
25
|
+
Encoded by: **rule id suffix**
|
|
26
|
+
|
|
27
|
+
Current intents:
|
|
28
|
+
|
|
29
|
+
- `present`
|
|
30
|
+
- Verifies that a required **mechanism exists**
|
|
31
|
+
- Typically automatic
|
|
32
|
+
- `quality`
|
|
33
|
+
- Verifies appropriateness, correctness, or meaningfulness
|
|
34
|
+
- Always manual
|
|
35
|
+
- `decorative`
|
|
36
|
+
- Verifies correct treatment of purely decorative content
|
|
37
|
+
- Always manual
|
|
38
|
+
|
|
39
|
+
Intent determines whether a rule can be automatic.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
### 1.3 Target Family
|
|
44
|
+
Encoded by: **rule id prefix**
|
|
45
|
+
|
|
46
|
+
Current families include:
|
|
47
|
+
|
|
48
|
+
- `img`
|
|
49
|
+
- `area`
|
|
50
|
+
- `input-image`
|
|
51
|
+
- `canvas`
|
|
52
|
+
- `embed`
|
|
53
|
+
- `object`
|
|
54
|
+
- `svg`
|
|
55
|
+
- `svg-image`
|
|
56
|
+
- `video-poster`
|
|
57
|
+
|
|
58
|
+
Each family defines:
|
|
59
|
+
- which elements are queried
|
|
60
|
+
- which helpers are applicable
|
|
61
|
+
- which mechanisms are valid
|
|
62
|
+
|
|
63
|
+
Rules MUST NOT mix families.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
### 1.4 Target Set (Tree Scope)
|
|
68
|
+
Encoded by: `data.visibilityFilter.targetSet`
|
|
69
|
+
|
|
70
|
+
Current value used by this ruleset:
|
|
71
|
+
- `acc` (accessibility tree)
|
|
72
|
+
|
|
73
|
+
Rules explicitly log eligibility against the accessibility tree using:
|
|
74
|
+
- `helpers.isAccTreeEligible`
|
|
75
|
+
- `helpers.getEligibilityInfo(..., { targetSet: "acc" })`
|
|
76
|
+
|
|
77
|
+
This is a semantic constraint, not logging noise.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### 1.5 Mechanism Type
|
|
82
|
+
Implicitly encoded via helper usage and coverage facets.
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
- attribute-based (`alt`, `poster`)
|
|
86
|
+
- IDREF-based (`aria-labelledby`, `aria-describedby`)
|
|
87
|
+
- fallback content (`<object>` contents)
|
|
88
|
+
- computed accessible name / description
|
|
89
|
+
- element-specific mechanisms (e.g. `<canvas>` alternatives)
|
|
90
|
+
|
|
91
|
+
Different mechanisms require separate rules or facets.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### 1.6 WCAG Mapping
|
|
96
|
+
Encoded by:
|
|
97
|
+
- `meta.wcagSc`
|
|
98
|
+
- `meta.normativeMappings`
|
|
99
|
+
- optional `meta.informativeReferences`
|
|
100
|
+
|
|
101
|
+
Multiple rules may map to the same SC.
|
|
102
|
+
This is required for atomicity.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
### 1.7 Coverage Facets
|
|
107
|
+
Encoded by: `meta.coverage.facetsBySc`
|
|
108
|
+
|
|
109
|
+
A **facet** represents one atomic coverage slice of a Success Criterion.
|
|
110
|
+
|
|
111
|
+
Rules MUST:
|
|
112
|
+
- declare at least one facet per SC
|
|
113
|
+
- not share facets with rules that make a different decision
|
|
114
|
+
- keep facet naming consistent within a family
|
|
115
|
+
|
|
116
|
+
Facets are used for coverage accounting, not reporting.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 2. Canonical rule family grid (example)
|
|
121
|
+
|
|
122
|
+
For image alternatives (WCAG 1.1.1):
|
|
123
|
+
|
|
124
|
+
| Rule ID | Family | Intent | Type |
|
|
125
|
+
|------|-------|-------|------|
|
|
126
|
+
| img-alt-present | img | present | automatic |
|
|
127
|
+
| img-alt-quality | img | quality | manual |
|
|
128
|
+
| img-alt-decorative | img | decorative | manual |
|
|
129
|
+
|
|
130
|
+
Other families follow the same grid where applicable.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 3. Atomicity rule (hard constraint)
|
|
135
|
+
|
|
136
|
+
If two checks differ by **any** of the following, they MUST be separate rules:
|
|
137
|
+
|
|
138
|
+
- normative vs review decision
|
|
139
|
+
- intent (present / quality / decorative)
|
|
140
|
+
- target family
|
|
141
|
+
- mechanism type
|
|
142
|
+
- outcome domain
|
|
143
|
+
- coverage facet
|
|
144
|
+
|
|
145
|
+
This constraint is already enforced by the existing ruleset.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Troubleshooting / FAQ
|
|
2
|
+
|
|
3
|
+
## "I passed `runOnly: ['some-rule-id']` but every rule still ran"
|
|
4
|
+
|
|
5
|
+
`runOnly` must be an object, not a bare array — `runOnly: ['img-alt-present']` is silently ignored (the engine falls through to "run everything"), because that shape has none of the fields the engine actually checks (`includeRuleIds`, `tags`, etc.). This is easy to get wrong if you're coming from another engine that does accept a bare array.
|
|
6
|
+
|
|
7
|
+
Fix:
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
runOnly: { includeRuleIds: ['img-alt-present'] }
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
See [`ENGINE_OPTIONS.md`](./ENGINE_OPTIONS.md#via-runonly-4th-argument) for the full shape.
|
|
14
|
+
|
|
15
|
+
## "My custom rule always returns `cantTell` with no clear reason"
|
|
16
|
+
|
|
17
|
+
Check the result's `error` field first — if it says `"<something> is not defined"`, your `runInPage` references a variable from outside the function body (a module-scope `const`, an imported helper, anything not reached through `ctx.*`). This is a real, common footgun: `runInPage` is serialized to source text and re-evaluated later in the page context, so **the build never catches this — only running the rule does**, and the failure looks like a normal (if uninformative) result, not a crash. See [`RULE_AUTHORING.md`](./RULE_AUTHORING.md) §1.1 for the full explanation and the fix (move the value inside `runInPage`, or route it through `ctx.rule`/`ctx.helpers`).
|
|
18
|
+
|
|
19
|
+
## "A geometry-dependent rule (e.g. `target-size-minimum`) always says `notApplicable`"
|
|
20
|
+
|
|
21
|
+
Plain jsdom (no real browser) doesn't implement CSS layout — `getBoundingClientRect()` always returns zero geometry. Rules that need real layout deliberately report `notApplicable` under jsdom rather than guess. Run through a real browser instead (Puppeteer/Playwright — see [`INTEGRATION.md`](./INTEGRATION.md) Pattern 2) to get real findings from these rules. See [`LIMITATIONS.md`](./LIMITATIONS.md).
|
|
22
|
+
|
|
23
|
+
## "I only see `fail`/`cantTell` occurrences — where's the list of elements that passed?"
|
|
24
|
+
|
|
25
|
+
By design, this engine never enumerates the elements a rule *passed* — only the ones it flagged. A rule's overall `outcome: 'pass'` means "applicable target(s) existed and none were flagged," but `occurrences` is `[]` either way. If you need to know which specific elements were checked and considered fine, that's not currently exposed — see [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md#an-occurrence-occurrencesi).
|
|
26
|
+
|
|
27
|
+
## "A rule I expected to fire returned `notApplicable` / found nothing on a page I know has the issue"
|
|
28
|
+
|
|
29
|
+
Two common causes, in order of likelihood:
|
|
30
|
+
|
|
31
|
+
1. **The element is excluded from the accessibility tree** — `aria-hidden="true"`, `display: none`, `visibility: hidden`, `hidden`, or an `inert` ancestor. Most rules deliberately skip content that's already invisible to assistive technology (checking a hidden element would be meaningless, and could produce a misleading `fail` on content no user encounters). Some rules explicitly opt out of this gating when it wouldn't make sense to (e.g. `no-autoplay-audio` — hidden audio still plays sound) — check the specific rule's file header comment (`@applicability`) in `src/checks/`.
|
|
32
|
+
2. **`excludeSelectors`** — if you've configured this (directly or inherited from a shared config), confirm the element in question isn't matched by it.
|
|
33
|
+
|
|
34
|
+
## "Does a clean scan (`pass` everywhere) mean the page is WCAG conformant?"
|
|
35
|
+
|
|
36
|
+
No — see [`WCAG_CONFORMANCE.md`](./WCAG_CONFORMANCE.md). A `pass` means every *automatable* check came back clean. A meaningful fraction of WCAG requires human judgment (accurate alt text, understandable error messages) or dynamic testing this engine's architecture can't do at all (keyboard traps, reflow at zoom) — see [`LIMITATIONS.md`](./LIMITATIONS.md) for the explicit, non-exhaustive-on-purpose list.
|
|
37
|
+
|
|
38
|
+
## "Should I treat `cantTell` as a failure?"
|
|
39
|
+
|
|
40
|
+
Treat it as "needs a human to look" — it's neither pass nor fail by design. Most teams log `cantTell` findings without failing CI on them, since failing a build on something the engine explicitly couldn't determine tends to train people to ignore the gate. See [`POLICY.md`](./POLICY.md) if you want to reshape this behavior (e.g. via a custom policy contract), and [`INTEGRATION.md`](./INTEGRATION.md#ci-gating-a-build-on-the-result) for a concrete CI-gating example.
|
|
41
|
+
|
|
42
|
+
## "Why is French only partially translated?"
|
|
43
|
+
|
|
44
|
+
It genuinely is — see [`I18N.md`](./I18N.md) for the exact current coverage. Missing keys fall back to English per-string (never a blank or broken result), so a partial locale degrades gracefully rather than failing outright.
|
|
45
|
+
|
|
46
|
+
## "`runDomRulesInPage` vs `runa11yCoreInPage` — which one do I want?"
|
|
47
|
+
|
|
48
|
+
`runDomRulesInPage` if you're calling it directly in the same Node process (jsdom, browser-extension content script). `runa11yCoreInPage` if you're handing the *function itself* to a different JS realm — almost always `page.evaluate` in Puppeteer/Playwright, which serializes the function to source text and re-runs it inside the browser tab (which has no access to your Node module scope). See [`INTEGRATION.md`](./INTEGRATION.md) for both patterns worked out in full.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# WCAG conformance mapping guide
|
|
2
|
+
|
|
3
|
+
How individual rule results relate to a WCAG Success Criterion (SC), and what surea11y can and cannot tell you about overall conformance.
|
|
4
|
+
|
|
5
|
+
## The three layers
|
|
6
|
+
|
|
7
|
+
1. **Atomic rules** (`checksResults[]`) — one normative decision each, e.g. "does this `<img>` have an `alt` attribute." See [`RULE_CATALOG.md`](./RULE_CATALOG.md) for all 123.
|
|
8
|
+
2. **Facets** — a WCAG SC is usually bigger than any one rule can decide deterministically. Internally, each SC is broken into named "facets" (e.g. 1.1.1 Non-text Content has facets like `img-alt-attr-present`, `text-alternative-quality`, `decorative-null`) tracked in `src/coverage/wcag-facets.js`, each marked `full` (a rule decides it with high confidence), `partial` (a rule decides *part* of it — see each rule's own scope notes), or `manual` (no safe automated heuristic exists at all). Run `npm run coverage` to regenerate `coverage/coverage-report.md`, the per-SC facet breakdown.
|
|
9
|
+
3. **Composite (WCAG-SC rollup) rules** (`rulesResults[]`) — a generated aggregate of every atomic rule mapped to one SC, giving you one pass/fail/cantTell/notApplicable verdict per SC instead of having to roll up dozens of atomic results yourself. See [`RULE_CATALOG.md`](./RULE_CATALOG.md#composite-wcag-sc-rollup-rules-31) for the full list (e.g. `wcag-1.1.1-non-text-content` rolls up 22 atomic rules).
|
|
10
|
+
|
|
11
|
+
## How a composite's outcome is computed
|
|
12
|
+
|
|
13
|
+
Deterministic precedence, evaluated over that composite's atomic contributors:
|
|
14
|
+
|
|
15
|
+
| Condition | Composite outcome |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Any contributor `fail` | `fail` |
|
|
18
|
+
| No `fail`, but any contributor `cantTell` **or** a listed contributor didn't run at all | `cantTell` |
|
|
19
|
+
| Every contributor `notApplicable` | `notApplicable` |
|
|
20
|
+
| Otherwise (all ran, none failed/cantTell, not all N/A) | `pass` |
|
|
21
|
+
|
|
22
|
+
This means: **a composite `pass` is a real, deterministic "every applicable automated check for this SC came back clean" — but it is not a WCAG conformance claim on its own.** If any facet of that SC has no automated coverage at all (see `coverage-report.md`), a composite `pass` is silent about that facet, not asserting it's fine. Cross-check the facet table before treating a composite `pass` as "SC fully verified."
|
|
23
|
+
|
|
24
|
+
A composite's `data.details.contributors` array (see [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md#a-composite-result-rulesresultsi)) lists every atomic rule and its individual outcome — use this to see exactly which facet(s) drove a `fail`/`cantTell`, rather than treating the composite as a black box.
|
|
25
|
+
|
|
26
|
+
## Targeting a conformance level (A / AA / AAA)
|
|
27
|
+
|
|
28
|
+
Pass `runOnly.tags` (or `engineOptions.tags.include`) with one of `wcag2a`, `wcag2aa`, `wcag2aaa`:
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
runDomRulesInPage(url, null, {}, { tags: ['wcag2aa'] });
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This does two things at once:
|
|
35
|
+
- **Filters atomic rules** to ones tagged at or under that level (a `wcag2aa`-tagged rule also carries `wcag2a`, since AA is cumulative on top of A — WCAG conformance is always defined this way).
|
|
36
|
+
- **Suppresses composites above the target level** — e.g. requesting `wcag2aa` will not return a `rulesResults` entry for an AAA-only SC's composite, even if some AAA-level atomic rules happen to be tagged loosely. This is `inferTargetLevelFromRunOnly`/`isAllowedByTargetLevel` in the runner — see `src/core/dom-runner.js` if you need the exact precedence logic.
|
|
37
|
+
|
|
38
|
+
Omit `tags` entirely (the default) and every rule at every level runs, with no composite suppression.
|
|
39
|
+
|
|
40
|
+
## What this engine cannot tell you
|
|
41
|
+
|
|
42
|
+
No automated tool — this one included — can certify full WCAG conformance. That's not a limitation specific to surea11y; it's inherent to WCAG itself; a meaningful fraction of Success Criteria require human judgment (is this alt text *accurate*, not just *present*; is this error message *understandable*) or dynamic testing this engine's static-DOM-scan architecture cannot do at all (keyboard-trap detection, real layout/reflow at zoom). See [`LIMITATIONS.md`](./LIMITATIONS.md) for the full, explicit list of what's out of scope and why.
|
|
43
|
+
|
|
44
|
+
What surea11y *can* give you, honestly:
|
|
45
|
+
- Every `fail` is a real, deterministic, normative violation — never a guess.
|
|
46
|
+
- Every `cantTell` is an explicit flag for human review, not a swallowed uncertainty.
|
|
47
|
+
- The facet coverage table tells you exactly which parts of which SCs have zero automated coverage, so you know where a `pass` is silent rather than exhaustive.
|
|
48
|
+
|
|
49
|
+
A composite `pass` across every SC at your target level means: *every automatable check for that level came back clean.* It is the automatable subset of conformance, stated precisely — not a substitute for the manual review WCAG itself requires.
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@surea11y/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lightweight DOM rules accessibility core with modular rules.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"surea11y": "./bin/core.js"
|
|
8
|
+
},
|
|
9
|
+
"author": "Jorge Rumoroso",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/rumoroso/surea11y-core.git"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/rumoroso/surea11y-core#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/rumoroso/surea11y-core/issues"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": "^20.19.0 || ^22.13.0 || >=24.0.0"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"src",
|
|
27
|
+
"bin",
|
|
28
|
+
"docs/**/*.md",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"CHANGELOG.md",
|
|
32
|
+
"!docs/RULE_TEMPLATE.md",
|
|
33
|
+
"!docs/RULE_TEST_TEMPLATE.md",
|
|
34
|
+
"!docs/RULE_TEST_AUTHORING.md",
|
|
35
|
+
"!docs/TEST_OUTCOME_STABILITY.md",
|
|
36
|
+
"!src/explain"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "node scripts/build-core.js",
|
|
40
|
+
"test": "npm run build && node scripts/run-tests.js",
|
|
41
|
+
"test:contrast-helpers": "node tests/contrast-helpers.test.js",
|
|
42
|
+
"helpers-perf-bench": "node --expose-gc scripts/dom-helpers-perf-bench.js",
|
|
43
|
+
"engine-perf-bench": "node --expose-gc scripts/engine-perf-bench.js --profileRules=true --iters=25 --top=15",
|
|
44
|
+
"engine-perf-bench-transparent": "node --expose-gc scripts/engine-perf-bench.js --profileRules=true --top=15 --iters=25 --generated=contrastTransparent",
|
|
45
|
+
"engine-perf-bench-opaque": "node --expose-gc scripts/engine-perf-bench.js --profileRules=true --top=15 --iters=25 --generated=contrastOpaque",
|
|
46
|
+
"test:typography-helpers": "node tests/contrast-typography.test.js",
|
|
47
|
+
"coverage": "node scripts/generate-wcag-coverage.js --rulesDir src/checks",
|
|
48
|
+
"coverage:strict": "node scripts/generate-wcag-coverage.js --rulesDir src/checks --strictFacets",
|
|
49
|
+
"fixtures:index": "node scripts/generate-fixture-index.js",
|
|
50
|
+
"docs:rule-catalog": "npm run build && node scripts/generate-rule-catalog.js",
|
|
51
|
+
"validate:automatic-rules": "npm run build && node scripts/validate-all-rules.js src/checks/automatic",
|
|
52
|
+
"validate:manual-rules": "npm run build && node scripts/validate-all-rules.js src/checks/manual"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"jsdom": "^29.1.1"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"playwright": "^1.61.1"
|
|
59
|
+
}
|
|
60
|
+
}
|