@surea11y/core 1.4.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 (49) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/bin/surea11y-core.js +0 -0
  3. package/docs/ARIA_DEPRECATION.md +95 -0
  4. package/docs/RULE_CATALOG.md +6 -6
  5. package/package.json +3 -1
  6. package/src/checks/automatic/aria-allowed-attr.js +648 -83
  7. package/src/checks/automatic/aria-deprecated-role.js +105 -37
  8. package/src/checks/automatic/aria-roles-valid.js +31 -6
  9. package/src/checks/automatic/autocomplete-valid.js +37 -1
  10. package/src/checks/automatic/avoid-inline-spacing.js +179 -19
  11. package/src/checks/automatic/binary-control-name-present.js +11 -3
  12. package/src/checks/automatic/button-name-present.js +50 -17
  13. package/src/checks/automatic/canvas-text-alternative-present.js +15 -8
  14. package/src/checks/automatic/combobox-name-present.js +8 -1
  15. package/src/checks/automatic/dialog-name-present.js +8 -1
  16. package/src/checks/automatic/form-control-programmatic-label-present.js +48 -4
  17. package/src/checks/automatic/form-control-single-label.js +107 -39
  18. package/src/checks/automatic/img-alt-present.js +16 -9
  19. package/src/checks/automatic/input-image-alt-present.js +99 -48
  20. package/src/checks/automatic/label-in-name.js +82 -8
  21. package/src/checks/automatic/language-page-present.js +5 -1
  22. package/src/checks/automatic/link-name-present.js +50 -17
  23. package/src/checks/automatic/listbox-name-present.js +8 -1
  24. package/src/checks/automatic/menuitem-name-present.js +8 -1
  25. package/src/checks/automatic/meta-refresh-no-exceptions.js +29 -0
  26. package/src/checks/automatic/meta-refresh-timing-absent.js +30 -4
  27. package/src/checks/automatic/meta-viewport-zoom-enabled.js +37 -15
  28. package/src/checks/automatic/meter-name-present.js +8 -1
  29. package/src/checks/automatic/nested-interactive-controls-absent.js +161 -31
  30. package/src/checks/automatic/object-text-alternative-present.js +14 -7
  31. package/src/checks/automatic/option-name-present.js +8 -1
  32. package/src/checks/automatic/progressbar-name-present.js +8 -1
  33. package/src/checks/automatic/searchbox-name-present.js +8 -1
  34. package/src/checks/automatic/slider-name-present.js +11 -2
  35. package/src/checks/automatic/spinbutton-name-present.js +8 -1
  36. package/src/checks/automatic/summary-name-present.js +8 -1
  37. package/src/checks/automatic/tab-name-present.js +8 -1
  38. package/src/checks/automatic/table-th-has-data-cells.js +67 -6
  39. package/src/checks/automatic/textbox-name-present.js +8 -1
  40. package/src/checks/automatic/tooltip-name-present.js +8 -1
  41. package/src/checks/automatic/treeitem-name-present.js +8 -1
  42. package/src/checks/automatic/valid-lang.js +16 -3
  43. package/src/checks/{automatic/bypass-blocks-present.js → manual/bypass-blocks-present-manual.js} +97 -35
  44. package/src/checks/manual/input-image-alt-decorative-manual.js +24 -0
  45. package/src/checks/manual/landmark-banner-is-top-level-manual.js +11 -22
  46. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +7 -10
  47. package/src/core.js +4853 -964
  48. package/src/sarif.js +2 -2
  49. package/surea11y.browser.js +2256 -433
package/CHANGELOG.md CHANGED
@@ -4,6 +4,47 @@ All notable changes to this project are documented here, in [Keep a Changelog](h
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.4.1] - 2026-08-13
8
+
9
+ ### Added
10
+ - `scripts/generate-aria-tables.js` generates `aria-allowed-attr`'s global/per-role/implicit-role tables from the `aria-query` package (a devDependency, output committed) instead of hand-maintaining them, with a `--check` mode that fails when the committed tables go stale.
11
+ - `scripts/generate-language-subtags.js` writes the IANA primary language subtags into `dom-helpers` from the `language-subtag-registry` package (a devDependency), same `--check` convention. New shared `helpers.isValidLanguageTag` backs both `valid-lang` and `html-lang-attr-present`.
12
+
13
+ ### Changed
14
+ - `form-control-single-label` now grades by whether surplus `<label>`s actually compete for the accessible name, instead of always failing on label count: passes when an `aria-labelledby`/`aria-label` override supersedes the native labels (they contribute nothing to the name); `cantTell` when one non-empty label is joined by empty label associations with no override; fails only when two or more non-empty labels genuinely compete. Uses the same shared `labelContributesAccessibleName` helper as `form-control-programmatic-label-present`, so the two agree. Clears a false positive on the Angular Material selectable-card pattern, where a card adds an empty `<label for>` on top of Material's own label while the control is named by `aria-label`. New `cantTell` locale strings in all four locales.
15
+ - `bypass-blocks-present` is now a manual rule (`cantTell`-capped) instead of automatic (`fail`-capable). WCAG 2.4.1 is about blocks "repeated on multiple Web pages" — whether a block is actually repeated across the site isn't decidable from one document, so a page that legitimately needs no bypass mechanism is indistinguishable from one that omits a required one. A single-snapshot scan can also catch a page mid-modal, when the real `<main>`/headings are correctly `aria-hidden`/`inert` for that state and only the dialog is exposed. Finding a recognized mechanism (a main landmark, a working same-page anchor, or a heading) still resolves to `notApplicable`; finding none now returns `cantTell` instead of asserting a violation the engine can't actually confirm. The same-page-anchor check is also now shadow-DOM-aware.
16
+ - The concrete and abstract ARIA role sets in `aria-helpers` are now generated from `aria-query` too, alongside the attribute tables, so Digital Publishing roles (`doc-biblioref`, etc.) are recognised instead of reported as unknown. Three ARIA 1.3 roles the package doesn't carry yet (`comment`, `suggestion`, `text`) are supplemented back.
17
+ - `aria-roles-valid` and `aria-deprecated-role` now skip programmatically hidden elements, where a role has no effect (ACT 674b10).
18
+ - `autocomplete-valid` now follows ACT 73f2c2's applicability: the `on`/`off` toggle, disabled controls (including `aria-disabled`), and input types with a fixed value (`submit`, `checkbox`, ...) are out of scope, since `autocomplete` can't describe a purpose for any of them.
19
+ - `meta-viewport-zoom-enabled` now applies only when `content` sets `maximum-scale` or `user-scalable` — setting neither can't restrict zoom, so there's nothing to judge.
20
+ - `aria-allowed-attr` now covers all 127 concrete ARIA roles instead of 35 hand-listed ones, so `button`, `link`, `img` and 27 other common roles are no longer skipped.
21
+ - Form-control naming is now split by native element vs. explicit ARIA role, so a control isn't reported by two rules at once: `binary-control-name-present`/`slider-name-present` are role-only, and `form-control-programmatic-label-present` skips a control whose explicit role has its own naming rule. An unlabelled checkbox used to produce two findings; now one.
22
+
23
+ ### Fixed
24
+ - `contrast-minimum`/`contrast-enhanced` no longer report text hidden with the sr-only clip technique (`clip: rect(0,0,0,0)`/`clip-path: inset(50%+)`, the pattern Angular CDK's live-announcer, Bootstrap's `.visually-hidden` and most `.sr-only` implementations use) — there's no visually-presented color to check. `opacity: 0` and off-screen positioning stay in scope, since either can be a single-property mistake on text meant to be visible, unlike the multi-property clip pattern.
25
+ - `aria-allowed-attr` failed the four ARIA 1.2 ex-globals (`aria-disabled`, `aria-errormessage`, `aria-haspopup`, `aria-invalid`) on any role that doesn't support them, but they're deprecated on that role, not prohibited — still allowed, just discouraged. Now reports `cantTell` (reason `ARIA_ATTR_DEPRECATED`) for those four instead of `fail`; a genuinely unsupported attribute (never a global, never deprecated on the role) still fails. Same "deprecated but allowed" treatment as the `role="generic"` fix below, applied to attributes instead of roles. New `cantTell` locale strings in all four locales.
26
+ - `aria-deprecated-role` failed `role="generic"`, but WAI-ARIA 1.2 §5.4 states that rule at SHOULD-NOT strength ("primarily for implementors of user agents") — the usage is conforming, so failing it was a false positive. Now reports `cantTell` under reason code `ARIA_ROLE_AUTHOR_DISCOURAGED`, alongside the existing deprecated-role case. `fail` is retained for a role carrying an author MUST NOT; no ARIA 1.2/1.3 role does, outside the abstract roles `aria-roles-valid` already covers. Reconciled the deprecation data (in `aria-helpers`, since `aria-query` doesn't carry it) against the full WAI-ARIA 1.2 role characteristics tables — see `docs/ARIA_DEPRECATION.md`.
27
+ - `nested-interactive-controls-absent` matched on role membership alone, so it failed a `role="listbox"` owning `role="option"` children, a `tablist` owning `tab`s, and every other composite widget whose managed children legitimately carry a widget role — an Angular Material autocomplete panel driven entirely via `aria-activedescendant`, with nothing inside it a separate focus target, was reported although WCAG 4.1.2's concern is two *operable* controls occupying one place. A descendant now counts only when it's also focusable (`helpers.getFocusableInfo`, accounting for `contenteditable`, `:disabled`, `inert`, invalid/negative `tabindex`); a genuinely focusable nested control (a `<button>` inside an `<a href>`, an option given its own `tabindex`) still fails.
28
+ - `nested-interactive-controls-absent`'s focusability gate (above) still over-flagged a composite widget using the roving-tabindex pattern, where the active owned child genuinely carries `tabindex="0"` and so passed the focusable check despite being a managed part of its container, not an independent nested control. Added an explicit owned-child map (`option`→`listbox`/`combobox`, `tab`→`tablist`, `treeitem`→`tree`, `menuitem(checkbox|radio)`→`menu`/`menubar`, `radio`→`radiogroup`); a child matching both the role and a matching ancestor container is exempt regardless of its own tabindex. An orphan role with no owning container (e.g. a stray `role="option"` outside any `listbox`) is unaffected and still counts if focusable.
29
+ - `avoid-inline-spacing` failed every inline `line-height`/`letter-spacing`/`word-spacing` declared `!important`, whatever the value — so `line-height: 2em !important` on 16px text was reported even though it already exceeds what WCAG 1.4.12 asks for. The criterion is about the resulting metrics, not the `!important` keyword: a forced value that already meets them leaves the user nothing to override. Now fails only below 1.5× font size for line-height, 0.12× for letter-spacing, 0.16× for word-spacing, taken from computed style where laid out and from the declared value otherwise. Applicability follows ACT: visible text of its own, rendered, not positioned off-screen; `inherit`/`unset` specify no spacing and are out of scope, `initial`/`revert` resolve to `normal` and stay in scope.
30
+ - `table-th-has-data-cells` failed every `<th>` in any table with zero `<td>`, so a `role="presentation"` layout table carrying a stray `<th>`, a table whose only header was `display:none`/`aria-hidden`, or a `<th role="cell">` were all reported. Per ACT d0f69e a header cell now counts only while visible, in the accessibility tree, and not overridden to a role other than `rowheader`/`columnheader`; an explicit `role="rowheader"`/`role="columnheader"` stays in scope.
31
+ - `label-in-name` compared label and name by character containment (`indexOf` on lowercased strings), which is wrong in both directions per WCAG 2.5.3's actual word-based algorithm: it accepted `aria-label="Discover Italy"` for the visible text "Discover It" (a substring, though "it" isn't the word "italy"), and rejected `aria-label="Search by date (YYYY-MM-DD)"` against "Search by date" or a label with decorative punctuation/emoji, though each matches once normalised. Now compares word lists — parenthesised text stripped, case-folded, NFKD-normalised, non-alphanumerics collapsed to spaces — and checks the label's words appear adjacent and in order in the name. An abbreviation or a differently-hyphenated word (`"University Ave."` vs `"University Avenue"`) now reports `cantTell` instead of failing, since neither is decidable from markup; a genuine mismatch alongside an uncertain one still fails. Two new locale strings in all four locales.
32
+ - `img-alt-present`, `object-text-alternative-present` and `canvas-text-alternative-present` used `isAccTreeEligible`, which keeps a tabbable element eligible under `aria-hidden`, so they reported elements no assistive technology can reach. All three now use `isIncludedInAccessibilityTree`, matching the `*-name-present` rules; a plain decorative `aria-hidden` image (the attribute's ordinary use) was already out of scope and is unaffected.
33
+ - `aria-roles-valid` judged only the first token of the `role` attribute, so `role="searchfield searchbox"` was reported invalid — `role` takes a fallback list, and a browser resolves it to the first token it recognises (`searchbox` here, confirmed against Chrome). The rule now fails only when no token names a concrete role.
34
+ - `autocomplete-valid` accepted a contact modality token before a non-contact field, so `"work photo"` passed even though a contact token is only valid when a contact field follows it (`"work email"` is fine, `"work photo"` isn't).
35
+ - `valid-lang` and `html-lang-attr-present` checked shape only (`/^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/`), so `lang="eng"` and `lang="em-US"` passed although neither is a registered primary subtag (the registry lists a three-letter code only when no two-letter one exists — that's why `"en"` is registered and `"eng"` isn't). Both now validate against the real IANA registry. `valid-lang` also now applies only where text actually inherits the language from the element, and a whitespace-only value fails.
36
+ - `meta-viewport-zoom-enabled` passed values it couldn't parse (`user-scalable=0.5`, `maximum-scale=invalid`, `maximum-scale=yes`), even though CSS Device Adaptation treats an unparseable value as `0` — which disables zoom exactly like an explicit `0` does. A negative `maximum-scale` is out of range and correctly still passes.
37
+ - `meta-refresh-timing-absent` and `meta-refresh-no-exceptions` reported directives a browser never acts on (`"foo; URL=x"`, `"+72001"`, `"0:1"`), since a malformed value makes the whole directive invalid rather than falling back to a default delay. Both now parse `content` with the shared declarative-refresh steps.
38
+ - `aria-allowed-attr` only looked at `[role]`, so an ARIA state/property on an element with no explicit role went unjudged (e.g. `<button aria-sort>`, `<p aria-checked>`) even though ACT 5c01ea applies to any element in the accessibility tree. The generator now also emits an implicit-role table, gated by an explicit allowlist of elements whose role is unconditional (verified against Chrome's own accessibility tree) so a future `aria-query` update can't silently widen the rule.
39
+ - `aria-allowed-attr` treated `aria-disabled`, `aria-haspopup`, `aria-invalid` and `aria-errormessage` as global attributes, so misuse on a role that doesn't support them went unreported; the generated table has the real 17 globals. It also no longer judges attributes against `role="none"`/`"presentation"`, since presentational role conflict resolution can drop that role on a focusable element — `presentation-role-conflict` already owns that case.
40
+ - `input-image-alt-present` treated `alt=""` as marking an image button decorative and passed it, but an image button is a control, not decoration — ACT 59796f requires a non-empty name, so an empty `alt` (or no `alt` at all) now fails. `alt=""` combined with `aria-label`/`aria-labelledby`/`title` still passes, since the control does have a name; that judgment call moved entirely to `input-image-alt-decorative`, so one element now yields one finding instead of two.
41
+ - `input-image-alt-present` now scopes to elements included in the accessibility tree (was the looser `isAccTreeEligible`), and rejects an accessible name equal to the browser's own fallback for an image button (`"Submit Query"`/`"Submit"`) as no name at all, per ACT 59796f. New locale strings for the fallback-name case in all four locales.
42
+ - `form-control-programmatic-label-present` used the looser `isAccTreeEligible` check, which keeps a focusable `aria-hidden` control "eligible", so it reported controls no assistive technology can reach. Switched to `isIncludedInAccessibilityTree`, matching the `*-name-present` rules and settling an existing inconsistency with `binary-control-name-present`, which already excluded the same case.
43
+ - The 18 `*-name-present` rules judged an element whether or not it was actually reachable by assistive technology, so a focusable element inside `aria-hidden` (both the tabbable and the IDREF-referenced case) got a real pass/fail verdict even though ACT's own glossary says such elements aren't in the accessibility tree at all — Chrome agrees, reporting `ignored: true` with no name. New shared `helpers.isIncludedInAccessibilityTree` routes all 18 rules to `notApplicable` there instead; the focus-order defect itself is still reported by `aria-hidden-focus`, which already owns it.
44
+ - `link-name-present`/`button-name-present` credited an accessible name from content regardless of an explicit `role`, so `<a href role="alert">Text</a>` passed even though Chrome computes an empty name for it. Naming from content is now gated by the ARIA 1.2 §5.2.8.5 allowlist of roles that actually support it; an unrecognised role falls back to the implicit `link`/`button` role and is unaffected. That allowlist initially missed module roles that *inherit* the behaviour from a superclass — `doc-noteref` inherits from `link`, so `<a role="doc-noteref"><sup>1</sup></a>` lost the name Chrome still reports (`"1"`) — so it's now generated from each role's `nameFrom` instead of hand-enumerated.
45
+ - `landmark-banner-is-top-level` and `landmark-contentinfo-is-top-level` selected any roleless `<header>`/`<footer>` as a candidate, regardless of nesting — but per HTML-AAM those elements have no banner/contentinfo role at all once descended from `article`/`aside`/`main`/`nav`/`section`, so there was no landmark there to be "nested". Both now select through the same suppression-aware role lookup the rest of the file already used, which cut false positives sharply on a sample corpus (236/236 for banner, 1/2 for contentinfo). An explicit `role="banner"`/`role="contentinfo"` is unaffected and still flagged when genuinely nested.
46
+ - `getContentNameInfo` let a descendant's `title` outrank its own text when computing a name from subtree content, so `<a title="T">Text</a>` could name itself `"T"` instead of `"Text"`. Content now wins over `title` unless the descendant's content is empty, matching Chrome and the accname spec. Same class of bug as the earlier `alt`-vs-`title` fix for image descendants, just never applied to the generic case.
47
+
7
48
  ## [1.4.0] - 2026-08-08
8
49
 
9
50
  ### Added
File without changes
@@ -0,0 +1,95 @@
1
+ <!-- SPDX-License-Identifier: MPL-2.0 -->
2
+
3
+ # ARIA deprecation handling
4
+
5
+ WAI-ARIA states two strengths of author rule. SHOULD NOT leaves the usage
6
+ conforming; MUST NOT does not. The engine grades on that distinction:
7
+ deprecated or otherwise discouraged usage resolves to **`cantTell`**, so the
8
+ author decides, and only prohibited usage `fail`s.
9
+
10
+ `aria-query` encodes no deprecation status — its role fields are `props`,
11
+ `requiredProps` and `prohibitedProps`, and it simply drops a deprecated
12
+ property from the role's `props`. Left alone that turns every deprecated
13
+ pairing into a not-allowed `fail`. The deprecation data therefore lives in a
14
+ spec-derived layer the engine owns, the same pattern as `SUPPLEMENTAL_GLOBALS`
15
+ (the 1.3 globals aria-query lacks).
16
+
17
+ ## The data layer — `src/core/aria-helpers.js`
18
+
19
+ Four sets and four predicates, consumed by `aria-allowed-attr` (attributes)
20
+ and `aria-deprecated-role` (roles):
21
+
22
+ ```
23
+ DEPRECATED_ATTRS // states/properties deprecated on roles that do not
24
+ // support them -> cantTell
25
+ DEPRECATED_ROLES // roles deprecated but valid -> cantTell
26
+ AUTHOR_DISCOURAGED_ROLES // roles reserved for user agents at SHOULD NOT
27
+ // strength -> cantTell
28
+ AUTHOR_PROHIBITED_ROLES // roles carrying an author MUST NOT -> fail
29
+
30
+ isDeprecatedAttr(attr, role) // role param reserved for per-role granularity
31
+ isDeprecatedRole(role)
32
+ isAuthorDiscouragedRole(role)
33
+ isAuthorProhibitedRole(role)
34
+ ```
35
+
36
+ ## Contents, reconciled against WAI-ARIA 1.2
37
+
38
+ - `DEPRECATED_ATTRS` = `aria-disabled`, `aria-errormessage`, `aria-haspopup`,
39
+ `aria-invalid`. ARIA 1.2 kept these four in the global set as deprecated
40
+ rather than removing them (change log, 07-May-2020), and marks each
41
+ "deprecated on this role" in the characteristics table of every role that
42
+ does not support it — 84 of the 94 role definitions carry at least one such
43
+ annotation. No role treats any of the four as prohibited, so the flat set
44
+ cannot downgrade a prohibited pairing to `cantTell`. These four are also the
45
+ only attributes annotated that way anywhere in the specification.
46
+ - `DEPRECATED_ROLES` = `directory`, the only role marked
47
+ `[Deprecated in ARIA 1.2]`, superseded by `list`.
48
+ - `AUTHOR_DISCOURAGED_ROLES` = `generic`, which §5.4 describes as "primarily
49
+ for implementors of user agents. Authors SHOULD NOT use this role in
50
+ content."
51
+ - `AUTHOR_PROHIBITED_ROLES` is empty. The only author MUST NOT covering roles
52
+ applies to the abstract roles, which `aria-roles-valid` already reports.
53
+
54
+ `aria-dropeffect` and `aria-grabbed` are deprecated in full (since ARIA 1.1)
55
+ but remain global in 1.2 and 1.3, so they are allowed on every role and pass.
56
+ Deliberately: naming them would flag markup no version of the specification
57
+ disallows, and the spec offers no replacement to move to — it records only
58
+ that one is "expected to be replaced by a new feature in a future version".
59
+
60
+ The properties ARIA does prohibit — `aria-label`, `aria-labelledby`, and
61
+ `aria-roledescription` on `generic` — are disjoint from the deprecated set and
62
+ are `aria-prohibited-attr`'s concern.
63
+
64
+ Four pairings pass rather than reporting `cantTell`: `aria-errormessage` and
65
+ `aria-invalid` on `menuitemcheckbox` and `menuitemradio`, which aria-query
66
+ lists among the role's supported properties while ARIA 1.2 marks them
67
+ deprecated there. The generated table follows aria-query, which errs towards
68
+ allowing the usage.
69
+
70
+ ## Verifying a spec revision
71
+
72
+ The role characteristics tables in the specification are machine-readable:
73
+ each role section carries `td.role-properties` (supported), `td.role-inherited`
74
+ (inherited) and `td.role-disallowed` (prohibited), and a deprecated entry is
75
+ suffixed "(deprecated on this role in ARIA 1.2)". Extracting those three cells
76
+ per role gives the full allowed/deprecated/prohibited matrix, which can be
77
+ diffed against the generated tables in `aria-allowed-attr` (`GLOBAL_ATTRS`,
78
+ `SUPPORTED_ATTRS_BY_ROLE`) plus `DEPRECATED_ATTRS` to confirm that no pairing
79
+ the specification allows or merely deprecates resolves to `fail`.
80
+
81
+ ## Applying a later revision
82
+
83
+ A specification change is a data edit; no rule logic changes:
84
+
85
+ - A deprecation promoted to prohibited: remove it from `DEPRECATED_ATTRS` or
86
+ `DEPRECATED_ROLES` so it falls back to the `fail` path, or move a role into
87
+ `AUTHOR_PROHIBITED_ROLES`.
88
+ - A new deprecation: add it to the relevant set.
89
+ - A deprecation that becomes per-role rather than uniform: `isDeprecatedAttr`
90
+ already receives the role, so a `(role, attr)` map replaces the flat set
91
+ behind the same predicate.
92
+
93
+ Then extend `tests/engine-checks/automatic/aria-allowed-attr.test.js` and
94
+ `aria-deprecated-role.test.js`, run `npm run build`, `node scripts/run-tests.js`,
95
+ `npm run format:check` and `npm run i18n:report`.
@@ -2,11 +2,11 @@
2
2
 
3
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
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.**
5
+ **125 rules total: 76 automatic (WCAG-normative, can return `fail`), 49 manual (advisory/judgment-required, capped at `cantTell`). 101 carry at least one formal WCAG Success Criterion mapping.**
6
6
 
7
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
8
 
9
- ## Automatic rules (77) — can return `fail`
9
+ ## Automatic rules (76) — can return `fail`
10
10
 
11
11
  | Rule ID | Title | WCAG SC | Level | Confidence | Default severity |
12
12
  |---|---|---|---|---|---|
@@ -15,7 +15,7 @@ See [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md) for what `type`/`confidence`/`sever
15
15
  | `aria-allowed-role` | Explicit role must be permitted for its host element | 4.1.2 | A | high | moderate |
16
16
  | `aria-braille-equivalent` | aria-braillelabel/aria-brailleroledescription must have a non-braille equivalent | 4.1.2 | A | high | serious |
17
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 |
18
+ | `aria-deprecated-role` | role attribute should not use a deprecated or author-discouraged ARIA role | 4.1.2 | A | high | moderate |
19
19
  | `aria-hidden-body` | The document &lt;body&gt; must not be aria-hidden | 1.3.1, 4.1.2 | A | high | critical |
20
20
  | `aria-hidden-focus` | ARIA hidden elements must not be focusable | 2.4.7, 4.1.2 | AA | high | serious |
21
21
  | `aria-prohibited-attr` | ARIA naming attributes must not be used on roles that prohibit them | 4.1.2 | A | high | moderate |
@@ -28,10 +28,9 @@ See [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md) for what `type`/`confidence`/`sever
28
28
  | `aria-valid-attr` | aria-* attributes must be real, defined ARIA attributes | 4.1.2 | A | high | serious |
29
29
  | `aria-valid-attr-value` | aria-* attribute values must match their declared type | 4.1.2 | A | high | serious |
30
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 |
31
+ | `avoid-inline-spacing` | Inline style must not force text spacing below the WCAG metric | 1.4.12 | AA | high | moderate |
32
32
  | `binary-control-name-present` | Binary controls have an accessible name | 4.1.2 | A | high | serious |
33
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
34
  | `canvas-text-alternative-present` | &lt;canvas&gt; must provide a text alternative | 1.1.1 | A | high | serious |
36
35
  | `combobox-name-present` | Comboboxes have an accessible name | 4.1.2 | A | high | serious |
37
36
  | `contrast-computable` | Color contrast is computable for rendered text | 1.4.3, 1.4.6 | AAA | high | serious |
@@ -88,7 +87,7 @@ See [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md) for what `type`/`confidence`/`sever
88
87
  | `valid-lang` | Element lang attribute must be syntactically valid | 3.1.2 | AA | high | moderate |
89
88
  | `video-poster-text-alternative-present` | &lt;video&gt; poster must have a text alternative | 1.1.1 | A | medium | serious |
90
89
 
91
- ## Manual rules (48) — advisory, capped at `cantTell`
90
+ ## Manual rules (49) — advisory, capped at `cantTell`
92
91
 
93
92
  | Rule ID | Title | WCAG SC | Level | Confidence | Default severity |
94
93
  |---|---|---|---|---|---|
@@ -97,6 +96,7 @@ See [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md) for what `type`/`confidence`/`sever
97
96
  | `area-alt-quality` | &lt;area&gt; alt text must be appropriate (manual review) | 1.1.1 | A | medium | minor |
98
97
  | `aria-checked-state-mismatch` | Native checkbox/radio aria-checked should match its actual state | 4.1.2 | A | medium | moderate |
99
98
  | `aria-text` | role="text" elements should have no focusable descendants | — | — | medium | minor |
99
+ | `bypass-blocks-present` | Page must provide a way to bypass repeated blocks | 2.4.1 | A | medium | moderate |
100
100
  | `canvas-text-alternative-quality` | &lt;canvas&gt; text alternative must be appropriate (manual review) | 1.1.1 | A | medium | minor |
101
101
  | `css-hidden-focus` | Focusable elements must not be visually hidden | 2.4.7 | AA | low | serious |
102
102
  | `embed-text-alternative-quality` | &lt;embed&gt; text alternative must be appropriate (manual review) | 1.1.1 | A | medium | minor |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@surea11y/core",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Deterministic WCAG 2.2 accessibility engine that tells you what it can't tell you. Zero dependencies.",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -96,10 +96,12 @@
96
96
  },
97
97
  "devDependencies": {
98
98
  "@eslint/js": "^10.0.1",
99
+ "aria-query": "^5.3.2",
99
100
  "eslint": "^10.8.0",
100
101
  "eslint-config-prettier": "^10.1.8",
101
102
  "globals": "^17.8.0",
102
103
  "jsdom": "^29.1.1",
104
+ "language-subtag-registry": "^0.4.2",
103
105
  "playwright": "^1.62.1",
104
106
  "prettier": "^3.9.6"
105
107
  }