pour-engine 1.44.2 → 1.46.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/config/project.config.js +1 -1
- package/engine/README.md +17 -0
- package/engine/index.js +13 -8
- package/engine/rules/wcag/1.4.1-link-in-text-block.js +13 -1
- package/engine/rules/wcag/1.4.3-color-contrast.js +4 -1
- package/engine/rules/wcag/2.4.7-focus-visible.js +49 -6
- package/engine/wcag3.js +520 -0
- package/package.json +1 -1
package/config/project.config.js
CHANGED
package/engine/README.md
CHANGED
|
@@ -111,6 +111,23 @@ pour engine therefore reports on **every** in-scope criterion, one of two ways:
|
|
|
111
111
|
in-scope criteria (filtered by the requested WCAG version and level)
|
|
112
112
|
that automation can't judge, for a host to render however it wants.
|
|
113
113
|
|
|
114
|
+
### WCAG 3 Working Draft
|
|
115
|
+
|
|
116
|
+
A check set tag, `wcag3-core` or `wcag3-supplemental`, reports against the
|
|
117
|
+
WCAG 3.0 Working Draft of 10 September 2026 instead (`wcag3.js`). The
|
|
118
|
+
rules are still WCAG 2 rules: each is matched, by reading both documents,
|
|
119
|
+
to the draft requirement its failure also fails, and a rule with no such
|
|
120
|
+
requirement does not run (`UNMAPPED_RULES` gives each reason). Where the
|
|
121
|
+
draft leaves a value to be determined, text contrast among them, the
|
|
122
|
+
rule's WCAG 2 value stands in. `wcag3-core` runs the rules matched to core
|
|
123
|
+
requirements, the ones the draft says must all be met to conform;
|
|
124
|
+
`wcag3-supplemental` adds the rules matched to supplemental requirements.
|
|
125
|
+
`results.standard` is stamped draft and names the set, `manualReview`
|
|
126
|
+
becomes the set's requirements, and each rule result carries `wcag3`.
|
|
127
|
+
The draft's Bronze, Silver and Gold sit above conformance and each needs
|
|
128
|
+
a number of supplemental requirements and assertions it has yet to
|
|
129
|
+
decide, so no check set stands for one. WCAG 2 stays the default.
|
|
130
|
+
|
|
114
131
|
The checklist accounts for the criteria; it does not mean every page state
|
|
115
132
|
or possible failure has been tested. A snapshot audit still needs human
|
|
116
133
|
assessment, including interaction states and any checks reported as skipped.
|
package/engine/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import config from '../config/project.config.js';
|
|
5
5
|
import rules from './rules/index.js';
|
|
6
6
|
import wcagCatalog from './wcag22.js';
|
|
7
|
+
import { ruleMatchesTags, checkSetFromTags, manualReviewChecklist, requirementsForRule, standardFor } from './wcag3.js';
|
|
7
8
|
import { isVisible, isRendered, cssPath, htmlSnippet, ownText, collectRoots, resetDOMCaches, releaseDOMCaches } from './lib/dom.js';
|
|
8
9
|
import { accessibleName } from './lib/accessible-name.js';
|
|
9
10
|
import { resetAuditCaches } from './lib/contrast.js';
|
|
@@ -13,12 +14,6 @@ export const version = config.engine.version;
|
|
|
13
14
|
|
|
14
15
|
const ruleHelpers = { isVisible, isRendered, cssPath, htmlSnippet, ownText, accessibleName };
|
|
15
16
|
|
|
16
|
-
/** Does this rule match the requested tag selection? Empty/absent = all rules. */
|
|
17
|
-
function ruleMatchesTags(rule, tags) {
|
|
18
|
-
if (!tags?.length) return true;
|
|
19
|
-
return rule.tags.some((tag) => tags.includes(tag));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
/** Which WCAG version/level was requested, judged from the selected tags. */
|
|
23
18
|
function scopeFromTags(tags) {
|
|
24
19
|
if (!tags?.length) return { version: '2.2', level: 'AAA' }; // no filter → everything
|
|
@@ -87,7 +82,12 @@ function toResultNode(element, outcome) {
|
|
|
87
82
|
* @param {Document|Element} context - what to scan
|
|
88
83
|
* @param {{ tags?: string[], exclude?: string, signal?: AbortSignal }} options -
|
|
89
84
|
* rule selection by tag (wcag2a…wcag22aa, best-practice), empty selects
|
|
90
|
-
* every rule;
|
|
85
|
+
* every rule; a WCAG 3 check set tag (wcag3-core, wcag3-supplemental)
|
|
86
|
+
* selects the rules matched to that set's draft requirements and
|
|
87
|
+
* reframes the report against the WCAG 3.0 Working Draft:
|
|
88
|
+
* results.standard is stamped draft, the manual checklist becomes the
|
|
89
|
+
* set's requirements,
|
|
90
|
+
* and each rule result carries the requirements it maps to (wcag3); exclude is a CSS selector — elements matching it, or inside
|
|
91
91
|
* a match (including across shadow boundaries), are left out of every
|
|
92
92
|
* rule; signal aborts the run between rules (throws AbortError)
|
|
93
93
|
* @param {(progress: object) => void} [onProgress] - called before each rule
|
|
@@ -98,15 +98,17 @@ function toResultNode(element, outcome) {
|
|
|
98
98
|
*/
|
|
99
99
|
export async function run(context = document, options = {}, onProgress) {
|
|
100
100
|
const auditStarted = performance.now();
|
|
101
|
+
const checkSet = checkSetFromTags(options.tags);
|
|
101
102
|
const results = {
|
|
102
103
|
testEngine: { name, version },
|
|
103
104
|
timestamp: new Date().toISOString(),
|
|
104
105
|
url: context.location?.href ?? context.ownerDocument?.location?.href ?? '',
|
|
106
|
+
standard: checkSet ? standardFor(checkSet) : null,
|
|
105
107
|
violations: [],
|
|
106
108
|
passes: [],
|
|
107
109
|
incomplete: [],
|
|
108
110
|
inapplicable: [],
|
|
109
|
-
manualReview: manualReviewCriteria(options.tags),
|
|
111
|
+
manualReview: checkSet ? manualReviewChecklist(checkSet) : manualReviewCriteria(options.tags),
|
|
110
112
|
ruleTimings: [],
|
|
111
113
|
};
|
|
112
114
|
|
|
@@ -251,6 +253,9 @@ export async function run(context = document, options = {}, onProgress) {
|
|
|
251
253
|
description: rule.help,
|
|
252
254
|
helpUrl: rule.helpUrl,
|
|
253
255
|
};
|
|
256
|
+
// In a WCAG 3 audit, the draft requirements this rule speaks to (empty
|
|
257
|
+
// for a best-practice rule, which maps to none).
|
|
258
|
+
if (checkSet) ruleResult.wcag3 = requirementsForRule(rule.id);
|
|
254
259
|
if (!elements.length) results.inapplicable.push({ ...ruleResult, nodes: [] });
|
|
255
260
|
if (buckets.fail.length) results.violations.push({ ...ruleResult, nodes: buckets.fail });
|
|
256
261
|
if (buckets.incomplete.length) results.incomplete.push({ ...ruleResult, nodes: buckets.incomplete });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// WCAG SC 1.4.1 Use of Color (Level A)
|
|
2
|
-
import { parseColor, contrastRatio, composite, effectiveBackground, backgroundImageSource } from '../../lib/contrast.js';
|
|
2
|
+
import { parseColor, contrastRatio, composite, effectiveBackground, backgroundImageSource, asRgb } from '../../lib/contrast.js';
|
|
3
3
|
|
|
4
4
|
// Split a computed list value ("a, b(c, d), e") on the commas OUTSIDE
|
|
5
5
|
// parentheses: gradients and colour functions carry commas of their own.
|
|
@@ -274,6 +274,17 @@ export default {
|
|
|
274
274
|
// WCAG technique G183: 3:1 against the surrounding text is a sufficient
|
|
275
275
|
// colour difference for identifying a link.
|
|
276
276
|
if (ratio >= 3) return { status: 'pass' };
|
|
277
|
+
// The pair the ratio was taken from, for the highlight card and the
|
|
278
|
+
// checker link: the link's colour and the prose's, as judged (composited
|
|
279
|
+
// where translucent), plus the background where it is a known solid.
|
|
280
|
+
const backdrop = effectiveBackground(parent);
|
|
281
|
+
const data = {
|
|
282
|
+
link: asRgb(linkColor),
|
|
283
|
+
surrounding: asRgb(textColor),
|
|
284
|
+
...(backdrop && (backdrop.a ?? 1) === 1 ? { background: asRgb(backdrop) } : {}),
|
|
285
|
+
ratio: Number(ratio.toFixed(2)),
|
|
286
|
+
required: 3,
|
|
287
|
+
};
|
|
277
288
|
|
|
278
289
|
// 2026-08-25 overnight audit: text-decoration propagates from an
|
|
279
290
|
// ancestor to its inline descendants and a descendant's "none" cannot
|
|
@@ -371,6 +382,7 @@ export default {
|
|
|
371
382
|
? 'This link has no underline and its colour differs from the surrounding text in hue alone, with no luminance difference: the distinction disappears entirely without colour vision, the exact failure F73 describes.'
|
|
372
383
|
: `This link has no underline and only ${shown}:1 colour difference from the surrounding text, below the 3:1 WCAG technique G183 asks for when colour is the only thing marking a link.`,
|
|
373
384
|
fix: 'Underline links inside text (text-decoration: underline), or add a non-colour indicator.',
|
|
385
|
+
data,
|
|
374
386
|
};
|
|
375
387
|
},
|
|
376
388
|
};
|
|
@@ -577,7 +577,7 @@ async function sampledVerdict(source, foreground, required, doc, overlays = [],
|
|
|
577
577
|
* 1.4.3 Contrast (Minimum), AA — 4.5:1, or 3:1 for large text
|
|
578
578
|
* 1.4.6 Contrast (Enhanced), AAA — 7:1, or 4.5:1 for large text
|
|
579
579
|
*/
|
|
580
|
-
export function createContrastRule({ id, tags, help, helpUrl, thresholds }) {
|
|
580
|
+
export function createContrastRule({ id, name, tags, help, helpUrl, thresholds }) {
|
|
581
581
|
// When the colour pair already clears the LARGE-scale minimum, the miss
|
|
582
582
|
// is about scale, not colour: name the boundary (14pt bold / 18pt, bold
|
|
583
583
|
// meaning BOLD_WEIGHT and up), or
|
|
@@ -608,6 +608,9 @@ export function createContrastRule({ id, tags, help, helpUrl, thresholds }) {
|
|
|
608
608
|
|
|
609
609
|
const rule = {
|
|
610
610
|
id,
|
|
611
|
+
// The display name both callers pass: dropped here until 2026-09-21, so
|
|
612
|
+
// the UI titled these two rules by their ids.
|
|
613
|
+
name,
|
|
611
614
|
impact: 'serious',
|
|
612
615
|
tags,
|
|
613
616
|
help,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// WCAG SC 2.4.7 Focus Visible (Level AA)
|
|
2
|
-
// A stylesheet that strips focus outlines without
|
|
3
|
-
// indicator is the classic 2.4.7 failure (F78).
|
|
4
|
-
// unreadable and skipped; a replacement indicator can
|
|
5
|
-
// rule entirely — so this flags for review, it never
|
|
2
|
+
// A stylesheet or style attribute that strips focus outlines without
|
|
3
|
+
// providing a replacement indicator is the classic 2.4.7 failure (F78).
|
|
4
|
+
// Cross-origin sheets are unreadable and skipped; a replacement indicator can
|
|
5
|
+
// live in a different rule entirely — so this flags for review, it never
|
|
6
|
+
// asserts a fail.
|
|
7
|
+
import { isRendered } from '../../lib/dom.js';
|
|
8
|
+
|
|
6
9
|
// The focus pseudo-classes themselves, and nothing else: :focus-within is a
|
|
7
10
|
// PARENT's state and stripping ":focus" out of it would leave "-within".
|
|
8
11
|
const FOCUS_TOKEN = /:focus(?:-visible)?(?![\w-])/g;
|
|
@@ -33,7 +36,8 @@ function focusSuppressors(doc) {
|
|
|
33
36
|
// human question.
|
|
34
37
|
let sheet = null;
|
|
35
38
|
const inspect = (rule) => {
|
|
36
|
-
if (!rule.selectorText
|
|
39
|
+
if (!rule.selectorText) return;
|
|
40
|
+
if (!/:focus/.test(rule.selectorText)) { inspectResting(rule); return; }
|
|
37
41
|
// `:focus:not(:focus-visible) { outline: none }` is the PUBLISHED pattern
|
|
38
42
|
// for showing the ring to keyboard users only: it strips the indicator
|
|
39
43
|
// precisely in the state where the browser has decided not to show focus,
|
|
@@ -65,7 +69,38 @@ function focusSuppressors(doc) {
|
|
|
65
69
|
provider.order > suspect.order && covered(subject, provider)));
|
|
66
70
|
if (!restored) suspects.push(suspect.selector);
|
|
67
71
|
}
|
|
72
|
+
allProviders.push(...sheet.providers);
|
|
73
|
+
};
|
|
74
|
+
// A rule with no focus state in it removes the indicator too: author
|
|
75
|
+
// styles outrank the browser's own :focus-visible ring whatever their
|
|
76
|
+
// specificity, so `* { outline: none }` or `a, button { outline: 0 }`
|
|
77
|
+
// leaves a focused link with no ring (measured in Chromium 2026-09-24).
|
|
78
|
+
// Such a rule matters only where it reaches something Tab lands on, and a
|
|
79
|
+
// box-shadow or border in the same rule is not a replacement: it is there
|
|
80
|
+
// at rest, so it marks nothing when focus arrives.
|
|
81
|
+
const resting = [];
|
|
82
|
+
const allProviders = [];
|
|
83
|
+
const tabbable = (el) => !el.disabled && (el.tabIndex >= 0 || el.isContentEditable) && isRendered(el);
|
|
84
|
+
const reachesTabbable = (part) => {
|
|
85
|
+
if (part.includes('::')) return false; // a pseudo-element's outline is not the control's ring
|
|
86
|
+
try { return [...doc.querySelectorAll(part)].some(tabbable); } catch { return false; }
|
|
87
|
+
};
|
|
88
|
+
const inspectResting = (rule) => {
|
|
89
|
+
const style = rule.style;
|
|
90
|
+
if (!style || !rule.selectorText) return;
|
|
91
|
+
const styles = [style, ...[...(rule.cssRules ?? [])]
|
|
92
|
+
.filter((child) => child.style && !child.selectorText).map((child) => child.style)];
|
|
93
|
+
if (!styles.map(outlineOf).some(removesOutline)) return;
|
|
94
|
+
const parts = subjects(rule.selectorText).filter(reachesTabbable);
|
|
95
|
+
if (parts.length) resting.push({ selector: rule.selectorText, subjects: parts });
|
|
68
96
|
};
|
|
97
|
+
// A focus rule outranks a resting one only when its specificity is
|
|
98
|
+
// higher, which order in the sheet cannot change. `a:focus-visible` always
|
|
99
|
+
// beats `a`; a bare `:focus-visible` (0,1,0) beats only selectors made of
|
|
100
|
+
// type names and `*`, and ties or loses to `.btn` or `.nav .btn`.
|
|
101
|
+
const typeOnly = (subject) => !/[#.[:]/.test(subject);
|
|
102
|
+
const outranked = (subject) => allProviders.some((provider) => provider.subjects.some((p) =>
|
|
103
|
+
p === subject || ((p === '' || p === '*') && typeOnly(subject))));
|
|
69
104
|
// Inspect each rule, THEN descend into any children. Testing `rule.cssRules`
|
|
70
105
|
// first and skipping used to walk straight past every plain style rule:
|
|
71
106
|
// since CSS Nesting shipped, a CSSStyleRule carries its own cssRules list,
|
|
@@ -84,6 +119,14 @@ function focusSuppressors(doc) {
|
|
|
84
119
|
try { scan(styleSheet.cssRules); } catch { /* cross-origin sheet */ }
|
|
85
120
|
settle();
|
|
86
121
|
}
|
|
122
|
+
for (const suspect of resting) {
|
|
123
|
+
if (!suspect.subjects.every(outranked)) suspects.push(suspect.selector);
|
|
124
|
+
}
|
|
125
|
+
// A style attribute outranks every selector, so no focus rule without
|
|
126
|
+
// !important gives the ring back to `<a style="outline: none">`.
|
|
127
|
+
for (const el of doc.querySelectorAll('[style]')) {
|
|
128
|
+
if (removesOutline(outlineOf(el.style)) && tabbable(el)) suspects.push(`the style attribute of <${el.localName}>`);
|
|
129
|
+
}
|
|
87
130
|
return suspects;
|
|
88
131
|
}
|
|
89
132
|
|
|
@@ -102,7 +145,7 @@ export default {
|
|
|
102
145
|
const shown = suspects.slice(0, 3).join(', ');
|
|
103
146
|
return {
|
|
104
147
|
status: 'incomplete',
|
|
105
|
-
message: `${suspects.length} CSS rule(s) remove the focus outline
|
|
148
|
+
message: `${suspects.length} CSS rule(s) or style attribute(s) remove the focus outline with no replacement found (e.g. ${shown}). If nothing else draws a visible indicator, keyboard users lose their place. Tab through the page to check.`,
|
|
106
149
|
};
|
|
107
150
|
},
|
|
108
151
|
};
|
package/engine/wcag3.js
ADDED
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
// The WCAG 3.0 Working Draft of 10 September 2026, as a catalog the engine
|
|
2
|
+
// can report against. https://www.w3.org/TR/2026/WD-wcag-3.0-20260910/
|
|
3
|
+
//
|
|
4
|
+
// WCAG 3.0 is unfinished. The W3C marks every section of it Developing or
|
|
5
|
+
// Exploratory and says the requirements will be added, combined, removed
|
|
6
|
+
// and reworded. Everything here is therefore stamped draft, and every
|
|
7
|
+
// surface that shows it must say so. WCAG 2 stays the default everywhere.
|
|
8
|
+
//
|
|
9
|
+
// The draft replaces success criteria and A/AA/AAA with three kinds of
|
|
10
|
+
// provision under each guideline: core requirements, supplemental
|
|
11
|
+
// requirements and assertions (documented statements about how content is
|
|
12
|
+
// made or how an organization works, which no tool can check). To conform
|
|
13
|
+
// (3.2), every core requirement must be met; supplemental requirements and
|
|
14
|
+
// assertions are not needed to conform. The draft's Reporting section (4,
|
|
15
|
+
// Exploratory) puts Bronze, Silver and Gold above conformance, each taking
|
|
16
|
+
// a number of supplemental requirements and assertions still to be
|
|
17
|
+
// decided, so none of them is a set of checks a tool can run. pour offers
|
|
18
|
+
// two check sets instead, and no tier:
|
|
19
|
+
// core: the rules matched to core requirements
|
|
20
|
+
// supplemental: those rules plus the ones matched to supplemental
|
|
21
|
+
// requirements
|
|
22
|
+
//
|
|
23
|
+
// No rule here was written for WCAG 3. The rules test WCAG 2 criteria;
|
|
24
|
+
// RULE_REQUIREMENTS names, rule by rule, the draft requirement that a
|
|
25
|
+
// failure of that rule also fails, judged by reading both documents. A
|
|
26
|
+
// rule with no such requirement (UNMAPPED_RULES, with the reason) does
|
|
27
|
+
// not run in a WCAG 3 audit. Where the draft leaves a value to be
|
|
28
|
+
// determined (text contrast, text spacing), the rule's WCAG 2 value
|
|
29
|
+
// stands in, and the report says the values are WCAG 2's.
|
|
30
|
+
|
|
31
|
+
export const draftInfo = {
|
|
32
|
+
id: 'wcag3-draft',
|
|
33
|
+
name: 'WCAG 3.0',
|
|
34
|
+
status: 'Working Draft',
|
|
35
|
+
dated: '2026-09-10',
|
|
36
|
+
draft: true,
|
|
37
|
+
url: 'https://www.w3.org/TR/2026/WD-wcag-3.0-20260910/',
|
|
38
|
+
note: 'An unfinished standard. Findings come from WCAG 2 rules matched to draft requirements, with WCAG 2 values where the draft has none yet. Not for conformance claims.',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// The check sets, cumulative, and the provision types each takes in.
|
|
42
|
+
export const CHECK_SETS = ['core', 'supplemental'];
|
|
43
|
+
const SET_TYPES = {
|
|
44
|
+
core: new Set(['core']),
|
|
45
|
+
supplemental: new Set(['core', 'supplemental']),
|
|
46
|
+
};
|
|
47
|
+
const SET_SCOPES = {
|
|
48
|
+
core: 'core requirements',
|
|
49
|
+
supplemental: 'core and supplemental requirements',
|
|
50
|
+
};
|
|
51
|
+
// Names the first WCAG 3 draft mode gave these sets, read the way they ran
|
|
52
|
+
// then, so a saved setting, a script or a tag list that still uses one
|
|
53
|
+
// selects the same rules. They are never shown.
|
|
54
|
+
const RENAMED_SETS = { bronze: 'core', silver: 'supplemental', gold: 'supplemental' };
|
|
55
|
+
|
|
56
|
+
const TYPE_LABELS = {
|
|
57
|
+
core: 'Core',
|
|
58
|
+
supplemental: 'Supplemental',
|
|
59
|
+
assertion: 'Assertion',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Every requirement and assertion of the draft, in document order, under
|
|
63
|
+
// its guideline. The one recommended practice (2.7.2.7) is left out: the
|
|
64
|
+
// draft says recommended practices are not needed to conform. Guidelines
|
|
65
|
+
// with nothing past Exploratory (2.1.2 Figure captions, 2.12.2 Control
|
|
66
|
+
// text) have no provisions to list.
|
|
67
|
+
const RAW = [
|
|
68
|
+
g('2.1.1', 'Image alternatives'),
|
|
69
|
+
p('2.1.1.1', 'Images detectable', 'core', 'x2-1-1-1-images-detectable'),
|
|
70
|
+
p('2.1.1.2', 'Decorative images hidden', 'core', 'x2-1-1-2-decorative-images-hidden'),
|
|
71
|
+
p('2.1.1.3', 'Image alternatives available', 'core', 'x2-1-1-3-image-alternatives-available'),
|
|
72
|
+
p('2.1.1.4', 'Image alternatives equivalent', 'core', 'x2-1-1-4-image-alternatives-equivalent'),
|
|
73
|
+
g('2.1.3', 'Non-text alternatives'),
|
|
74
|
+
p('2.1.3.1', 'Non-text content not relied on', 'core', 'x2-1-3-1-non-text-content-not-relied-on'),
|
|
75
|
+
g('2.1.4', 'Transcripts'),
|
|
76
|
+
p('2.1.4.1', 'Transcripts findable', 'core', 'x2-1-4-1-transcripts-findable'),
|
|
77
|
+
p('2.1.4.2', 'Dialogue transcripts available (prerecorded)', 'core', 'x2-1-4-2-dialogue-transcripts-available-prerecorded'),
|
|
78
|
+
p('2.1.4.3', 'Dialogue transcripts available (live)', 'core', 'x2-1-4-3-dialogue-transcripts-available-live'),
|
|
79
|
+
p('2.1.4.4', 'Transcripts equivalent (prerecorded)', 'core', 'x2-1-4-4-transcripts-equivalent-prerecorded'),
|
|
80
|
+
p('2.1.4.5', 'Descriptive transcripts available', 'core', 'x2-1-4-5-descriptive-transcripts-available'),
|
|
81
|
+
p('2.1.4.6', 'Speakers identified in transcripts', 'supplemental', 'x2-1-4-6-speakers-identified-in-transcripts'),
|
|
82
|
+
p('2.1.4.7', 'Speaker language identified in transcripts', 'supplemental', 'x2-1-4-7-speaker-language-identified-in-transcripts'),
|
|
83
|
+
p('2.1.4.8', 'Sounds identified in transcripts', 'core', 'x2-1-4-8-sounds-identified-in-transcripts'),
|
|
84
|
+
p('2.1.4.9', 'Visual information identified in transcripts', 'core', 'x2-1-4-9-visual-information-identified-in-transcripts'),
|
|
85
|
+
p('2.1.4.10', 'Transcripts style guide', 'assertion', 'x2-1-4-10-transcripts-style-guide'),
|
|
86
|
+
p('2.1.4.11', 'Transcripts usability testing', 'assertion', 'x2-1-4-11-transcripts-usability-testing'),
|
|
87
|
+
p('2.1.4.12', 'Transcripts reviewed by content authors', 'assertion', 'x2-1-4-12-transcripts-reviewed-by-content-authors'),
|
|
88
|
+
g('2.1.5', 'Captions'),
|
|
89
|
+
p('2.1.5.1', 'Captions adjustable', 'supplemental', 'x2-1-5-1-captions-adjustable'),
|
|
90
|
+
p('2.1.5.2', 'Captions available (prerecorded)', 'core', 'x2-1-5-2-captions-available-prerecorded'),
|
|
91
|
+
p('2.1.5.3', 'Captions equivalent (prerecorded)', 'core', 'x2-1-5-3-captions-equivalent-prerecorded'),
|
|
92
|
+
p('2.1.5.4', 'Captions available (live)', 'core', 'x2-1-5-4-captions-available-live'),
|
|
93
|
+
p('2.1.5.5', 'Caption language adjustable', 'supplemental', 'x2-1-5-5-caption-language-adjustable'),
|
|
94
|
+
p('2.1.5.6', 'Captions controllable', 'supplemental', 'x2-1-5-6-captions-controllable'),
|
|
95
|
+
p('2.1.5.7', 'Captions unobstructed', 'supplemental', 'x2-1-5-7-captions-unobstructed'),
|
|
96
|
+
p('2.1.5.8', 'Captions synchronized', 'core', 'x2-1-5-8-captions-synchronized'),
|
|
97
|
+
p('2.1.5.9', 'Captions centered (immersive)', 'core', 'x2-1-5-9-captions-centered-immersive'),
|
|
98
|
+
p('2.1.5.10', 'Direction indicated (immersive)', 'core', 'x2-1-5-10-direction-indicated-immersive'),
|
|
99
|
+
p('2.1.5.11', 'Speakers identified in captions', 'supplemental', 'x2-1-5-11-speakers-identified-in-captions'),
|
|
100
|
+
p('2.1.5.12', 'Speaker language identified in captions', 'supplemental', 'x2-1-5-12-speaker-language-identified-in-captions'),
|
|
101
|
+
p('2.1.5.13', 'Sounds identified in captions', 'core', 'x2-1-5-13-sounds-identified-in-captions'),
|
|
102
|
+
p('2.1.5.14', 'Captions style guide', 'assertion', 'x2-1-5-14-captions-style-guide'),
|
|
103
|
+
p('2.1.5.15', 'Captions usability testing', 'assertion', 'x2-1-5-15-captions-usability-testing'),
|
|
104
|
+
p('2.1.5.16', 'Captions reviewed by content authors', 'assertion', 'x2-1-5-16-captions-reviewed-by-content-authors'),
|
|
105
|
+
g('2.1.6', 'Audio descriptions'),
|
|
106
|
+
p('2.1.6.1', 'Audio descriptions available (prerecorded)', 'core', 'x2-1-6-1-audio-descriptions-available-prerecorded'),
|
|
107
|
+
p('2.1.6.2', 'Audio descriptions equivalent (prerecorded)', 'core', 'x2-1-6-2-audio-descriptions-equivalent-prerecorded'),
|
|
108
|
+
p('2.1.6.3', 'Audio descriptions synchronized', 'core', 'x2-1-6-3-audio-descriptions-synchronized'),
|
|
109
|
+
p('2.1.6.4', 'Audio descriptions available (live)', 'supplemental', 'x2-1-6-4-audio-descriptions-available-live'),
|
|
110
|
+
p('2.1.6.5', 'Extended audio descriptions available', 'core', 'x2-1-6-5-extended-audio-descriptions-available'),
|
|
111
|
+
p('2.1.6.6', 'Extended audio descriptions equivalent', 'core', 'x2-1-6-6-extended-audio-descriptions-equivalent'),
|
|
112
|
+
p('2.1.6.7', 'Audio description language adjustable', 'supplemental', 'x2-1-6-7-audio-description-language-adjustable'),
|
|
113
|
+
p('2.1.6.8', 'Audio descriptions controllable', 'supplemental', 'x2-1-6-8-audio-descriptions-controllable'),
|
|
114
|
+
p('2.1.6.9', 'Speakers identified in audio descriptions', 'supplemental', 'x2-1-6-9-speakers-identified-in-audio-descriptions'),
|
|
115
|
+
p('2.1.6.10', 'Speaker language identified in audio descriptions', 'supplemental', 'x2-1-6-10-speaker-language-identified-in-audio-descriptions'),
|
|
116
|
+
p('2.1.6.11', 'Sounds identified in audio descriptions', 'core', 'x2-1-6-11-sounds-identified-in-audio-descriptions'),
|
|
117
|
+
p('2.1.6.12', 'Visual information identified in audio descriptions', 'core', 'x2-1-6-12-visual-information-identified-in-audio-descriptions'),
|
|
118
|
+
p('2.1.6.13', 'Audio descriptions style guide', 'assertion', 'x2-1-6-13-audio-descriptions-style-guide'),
|
|
119
|
+
p('2.1.6.14', 'Audio descriptions usability testing', 'assertion', 'x2-1-6-14-audio-descriptions-usability-testing'),
|
|
120
|
+
p('2.1.6.15', 'Audio descriptions reviewed by content authors', 'assertion', 'x2-1-6-15-audio-descriptions-reviewed-by-content-authors'),
|
|
121
|
+
g('2.1.7', 'Sign language'),
|
|
122
|
+
p('2.1.7.1', 'Sign language available (prerecorded)', 'supplemental', 'x2-1-7-1-sign-language-available-prerecorded'),
|
|
123
|
+
p('2.1.7.2', 'Sign language controllable', 'supplemental', 'x2-1-7-2-sign-language-controllable'),
|
|
124
|
+
p('2.1.7.3', 'Sign language policy (live)', 'assertion', 'x2-1-7-3-sign-language-policy-live'),
|
|
125
|
+
g('2.1.8', 'Single sense'),
|
|
126
|
+
p('2.1.8.1', 'Hue not relied on', 'core', 'x2-1-8-1-hue-not-relied-on'),
|
|
127
|
+
p('2.1.8.2', 'Graphical object contrast sufficient', 'core', 'x2-1-8-2-graphical-object-contrast-sufficient'),
|
|
128
|
+
p('2.1.8.3', 'Visual depth not relied on', 'core', 'x2-1-8-3-visual-depth-not-relied-on'),
|
|
129
|
+
p('2.1.8.4', 'Sound not relied on', 'core', 'x2-1-8-4-sound-not-relied-on'),
|
|
130
|
+
p('2.1.8.5', 'Spatial audio not relied on', 'core', 'x2-1-8-5-spatial-audio-not-relied-on'),
|
|
131
|
+
g('2.1.9', 'Accessible media player'),
|
|
132
|
+
p('2.1.9.1', 'Accessible video player selected', 'assertion', 'x2-1-9-1-accessible-video-player-selected'),
|
|
133
|
+
p('2.1.9.2', 'Accessible audio player selected', 'assertion', 'x2-1-9-2-accessible-audio-player-selected'),
|
|
134
|
+
g('2.2.1', 'Text appearance'),
|
|
135
|
+
p('2.2.1.1', 'Blocks of text readable (minimum)', 'core', 'x2-2-1-1-blocks-of-text-readable-minimum'),
|
|
136
|
+
p('2.2.1.2', 'Text style readable (minimum)', 'core', 'x2-2-1-2-text-style-readable-minimum'),
|
|
137
|
+
p('2.2.1.3', 'Text contrast sufficient (minimum)', 'core', 'x2-2-1-3-text-contrast-sufficient-minimum'),
|
|
138
|
+
p('2.2.1.4', 'Blocks of text adjustable', 'core', 'x2-2-1-4-blocks-of-text-adjustable'),
|
|
139
|
+
p('2.2.1.5', 'Text style adjustable', 'core', 'x2-2-1-5-text-style-adjustable'),
|
|
140
|
+
p('2.2.1.6', 'Text size adjustable', 'core', 'x2-2-1-6-text-size-adjustable'),
|
|
141
|
+
p('2.2.1.7', 'Text color adjustable', 'core', 'x2-2-1-7-text-color-adjustable'),
|
|
142
|
+
p('2.2.1.8', 'Blocks of text readable (enhanced)', 'supplemental', 'x2-2-1-8-blocks-of-text-readable-enhanced'),
|
|
143
|
+
p('2.2.1.9', 'Text style readable (enhanced)', 'supplemental', 'x2-2-1-9-text-style-readable-enhanced'),
|
|
144
|
+
p('2.2.1.10', 'Text contrast sufficient (enhanced)', 'supplemental', 'x2-2-1-10-text-contrast-sufficient-enhanced'),
|
|
145
|
+
p('2.2.1.11', 'Text customizations retained', 'supplemental', 'x2-2-1-11-text-customizations-retained'),
|
|
146
|
+
g('2.2.2', 'Text-to-speech'),
|
|
147
|
+
p('2.2.2.1', 'Text detectable', 'core', 'x2-2-2-1-text-detectable'),
|
|
148
|
+
p('2.2.2.2', 'Human language detectable', 'core', 'x2-2-2-2-human-language-detectable'),
|
|
149
|
+
p('2.2.2.3', 'Numerical metadata available', 'core', 'x2-2-2-3-numerical-metadata-available'),
|
|
150
|
+
g('2.2.3', 'Clear language'),
|
|
151
|
+
p('2.2.3.1', 'Abbreviations explained', 'core', 'x2-2-3-1-abbreviations-explained'),
|
|
152
|
+
p('2.2.3.2', 'Non-literal language explained', 'supplemental', 'x2-2-3-2-non-literal-language-explained'),
|
|
153
|
+
p('2.2.3.3', 'Summaries available', 'core', 'x2-2-3-3-summaries-available'),
|
|
154
|
+
p('2.2.3.4', 'Common words used', 'supplemental', 'x2-2-3-4-common-words-used'),
|
|
155
|
+
p('2.2.3.5', 'Diacritics available', 'core', 'x2-2-3-5-diacritics-available'),
|
|
156
|
+
p('2.2.3.6', 'No nested clauses', 'supplemental', 'x2-2-3-6-no-nested-clauses'),
|
|
157
|
+
p('2.2.3.7', 'No unnecessary words', 'supplemental', 'x2-2-3-7-no-unnecessary-words'),
|
|
158
|
+
p('2.2.3.8', 'Clear language review', 'assertion', 'x2-2-3-8-clear-language-review'),
|
|
159
|
+
p('2.2.3.9', 'Visual aids review', 'assertion', 'x2-2-3-9-visual-aids-review'),
|
|
160
|
+
g('2.3.1', 'Keyboard focus appearance'),
|
|
161
|
+
p('2.3.1.1', 'Default focus indicator used', 'supplemental', 'x2-3-1-1-default-focus-indicator-used'),
|
|
162
|
+
p('2.3.1.2', 'Focus indicator contrast sufficient', 'core', 'x2-3-1-2-focus-indicator-contrast-sufficient'),
|
|
163
|
+
p('2.3.1.3', 'Focus indicator size sufficient', 'supplemental', 'x2-3-1-3-focus-indicator-size-sufficient'),
|
|
164
|
+
p('2.3.1.4', 'Focus indicator style guide', 'assertion', 'x2-3-1-4-focus-indicator-style-guide'),
|
|
165
|
+
g('2.3.2', 'Pointer focus appearance'),
|
|
166
|
+
p('2.3.2.1', 'Pointer activation indicated (minimum)', 'core', 'x2-3-2-1-pointer-activation-indicated-minimum'),
|
|
167
|
+
p('2.3.2.2', 'Pointer activation indicated (enhanced)', 'supplemental', 'x2-3-2-2-pointer-activation-indicated-enhanced'),
|
|
168
|
+
p('2.3.2.3', 'Pointer contrast sufficient', 'core', 'x2-3-2-3-pointer-contrast-sufficient'),
|
|
169
|
+
p('2.3.2.4', 'Default pointer used', 'supplemental', 'x2-3-2-4-default-pointer-used'),
|
|
170
|
+
p('2.3.2.5', 'Pointer focus indicated', 'core', 'x2-3-2-5-pointer-focus-indicated'),
|
|
171
|
+
p('2.3.2.6', 'Pointer visible', 'core', 'x2-3-2-6-pointer-visible'),
|
|
172
|
+
p('2.3.2.7', 'Enhanced pointer available', 'supplemental', 'x2-3-2-7-enhanced-pointer-available'),
|
|
173
|
+
g('2.3.3', 'Navigating content'),
|
|
174
|
+
p('2.3.3.1', 'Focus relevant', 'core', 'x2-3-3-1-focus-relevant'),
|
|
175
|
+
p('2.3.3.2', 'Focus retained', 'supplemental', 'x2-3-3-2-focus-retained'),
|
|
176
|
+
p('2.3.3.3', 'Focus order meaningful', 'core', 'x2-3-3-3-focus-order-meaningful'),
|
|
177
|
+
g('2.3.4', 'Expected behavior'),
|
|
178
|
+
p('2.3.4.1', 'Consistent interactions', 'assertion', 'x2-3-4-1-consistent-interactions'),
|
|
179
|
+
p('2.3.4.2', 'Consistent control location', 'supplemental', 'x2-3-4-2-consistent-control-location'),
|
|
180
|
+
p('2.3.4.3', 'Conventional pattern used', 'assertion', 'x2-3-4-3-conventional-pattern-used'),
|
|
181
|
+
g('2.3.5', 'Control information'),
|
|
182
|
+
p('2.3.5.1', 'Interactive element contrast sufficient', 'core', 'x2-3-5-1-interactive-element-contrast-sufficient'),
|
|
183
|
+
p('2.3.5.2', 'Interactive element names available', 'core', 'x2-3-5-2-interactive-element-names-available'),
|
|
184
|
+
p('2.3.5.3', 'Changes to elements notified', 'core', 'x2-3-5-3-changes-to-elements-notified'),
|
|
185
|
+
p('2.3.5.4', 'Input constraints used', 'core', 'x2-3-5-4-input-constraints-used'),
|
|
186
|
+
p('2.3.5.5', 'Label included in programmatic name', 'core', 'x2-3-5-5-label-included-in-programmatic-name'),
|
|
187
|
+
p('2.3.5.6', 'Roles, values, states, properties available', 'core', 'x2-3-5-6-roles-values-states-properties-available'),
|
|
188
|
+
g('2.4.1', 'Keyboard interface input'),
|
|
189
|
+
p('2.4.1.1', 'Keyboard operable', 'core', 'x2-4-1-1-keyboard-operable'),
|
|
190
|
+
p('2.4.1.2', 'Keyboard accessible', 'core', 'x2-4-1-2-keyboard-accessible'),
|
|
191
|
+
p('2.4.1.3', 'Bidirectional navigation', 'core', 'x2-4-1-3-bidirectional-navigation'),
|
|
192
|
+
p('2.4.1.4', 'Custom keys documented', 'supplemental', 'x2-4-1-4-custom-keys-documented'),
|
|
193
|
+
p('2.4.1.5', 'No keyboard conflicts', 'core', 'x2-4-1-5-no-keyboard-conflicts'),
|
|
194
|
+
p('2.4.1.6', 'Focus placed', 'supplemental', 'x2-4-1-6-focus-placed'),
|
|
195
|
+
p('2.4.1.7', 'No keyboard traps', 'core', 'x2-4-1-7-no-keyboard-traps'),
|
|
196
|
+
p('2.4.1.8', 'Focus user-controlled', 'core', 'x2-4-1-8-focus-user-controlled'),
|
|
197
|
+
p('2.4.1.9', 'Focus movement relevant', 'supplemental', 'x2-4-1-9-focus-movement-relevant'),
|
|
198
|
+
g('2.4.2', 'Physical or cognitive effort when using keyboard'),
|
|
199
|
+
p('2.4.2.1', 'Navigation keys described', 'supplemental', 'x2-4-2-1-navigation-keys-described'),
|
|
200
|
+
p('2.4.2.2', 'No repetitive adjacent interactive elements', 'supplemental', 'x2-4-2-2-no-repetitive-adjacent-interactive-elements'),
|
|
201
|
+
p('2.4.2.3', 'Keyboard effort comparable', 'assertion', 'x2-4-2-3-keyboard-effort-comparable'),
|
|
202
|
+
g('2.4.3', 'Pointer input'),
|
|
203
|
+
p('2.4.3.1', 'Pointer activation controllable', 'core', 'x2-4-3-1-pointer-activation-controllable'),
|
|
204
|
+
p('2.4.3.2', 'Simple pointer input available', 'core', 'x2-4-3-2-simple-pointer-input-available'),
|
|
205
|
+
p('2.4.3.3', 'Consistent pointer cancellation', 'supplemental', 'x2-4-3-3-consistent-pointer-cancellation'),
|
|
206
|
+
p('2.4.3.4', 'Pointer pressure not relied on', 'core', 'x2-4-3-4-pointer-pressure-not-relied-on'),
|
|
207
|
+
p('2.4.3.5', 'Pointer speed not relied on', 'core', 'x2-4-3-5-pointer-speed-not-relied-on'),
|
|
208
|
+
g('2.4.4', 'Speech and voice input'),
|
|
209
|
+
p('2.4.4.1', 'Speech not relied on', 'core', 'x2-4-4-1-speech-not-relied-on'),
|
|
210
|
+
p('2.4.4.2', 'Real-time text available', 'core', 'x2-4-4-2-real-time-text-available'),
|
|
211
|
+
p('2.4.4.3', 'Generated speech testing', 'assertion', 'x2-4-4-3-generated-speech-testing'),
|
|
212
|
+
g('2.4.5', 'Input operation'),
|
|
213
|
+
p('2.4.5.1', 'Hover or focus content dismissible', 'core', 'x2-4-5-1-hover-or-focus-content-dismissible'),
|
|
214
|
+
p('2.4.5.2', 'Hover content persistent', 'core', 'x2-4-5-2-hover-content-persistent'),
|
|
215
|
+
p('2.4.5.3', 'Hover or focus content persistent', 'supplemental', 'x2-4-5-3-hover-or-focus-content-persistent'),
|
|
216
|
+
p('2.4.5.4', 'Path-based gesture not relied on', 'core', 'x2-4-5-4-path-based-gesture-not-relied-on'),
|
|
217
|
+
p('2.4.5.5', 'Input method flexible', 'supplemental', 'x2-4-5-5-input-method-flexible'),
|
|
218
|
+
p('2.4.5.6', 'Body movements not relied on', 'core', 'x2-4-5-6-body-movements-not-relied-on'),
|
|
219
|
+
p('2.4.5.7', 'Eye tracking not relied on', 'core', 'x2-4-5-7-eye-tracking-not-relied-on'),
|
|
220
|
+
p('2.4.5.8', 'Pointer accessible', 'core', 'x2-4-5-8-pointer-accessible'),
|
|
221
|
+
g('2.4.6', 'Authentication'),
|
|
222
|
+
p('2.4.6.1', 'Biometrics not relied on', 'core', 'x2-4-6-1-biometrics-not-relied-on'),
|
|
223
|
+
p('2.4.6.2', 'Voice identification not relied on', 'core', 'x2-4-6-2-voice-identification-not-relied-on'),
|
|
224
|
+
g('2.5.1', 'Correct errors'),
|
|
225
|
+
p('2.5.1.1', 'Error notifications available', 'core', 'x2-5-1-1-error-notifications-available'),
|
|
226
|
+
p('2.5.1.2', 'Error suggestions provided', 'supplemental', 'x2-5-1-2-error-suggestions-provided'),
|
|
227
|
+
p('2.5.1.3', 'Errors indicated in multiple ways', 'supplemental', 'x2-5-1-3-errors-indicated-in-multiple-ways'),
|
|
228
|
+
p('2.5.1.4', 'Error messages persistent', 'supplemental', 'x2-5-1-4-error-messages-persistent'),
|
|
229
|
+
p('2.5.1.5', 'Errors associated', 'core', 'x2-5-1-5-errors-associated'),
|
|
230
|
+
p('2.5.1.6', 'Error messages collocated', 'supplemental', 'x2-5-1-6-error-messages-collocated'),
|
|
231
|
+
g('2.5.2', 'Prevent errors'),
|
|
232
|
+
p('2.5.2.1', 'Errors preventable', 'core', 'x2-5-2-1-errors-preventable'),
|
|
233
|
+
p('2.5.2.2', 'Submission status notified', 'supplemental', 'x2-5-2-2-submission-status-notified'),
|
|
234
|
+
p('2.5.2.3', 'Data entry validated', 'supplemental', 'x2-5-2-3-data-entry-validated'),
|
|
235
|
+
p('2.5.2.4', 'Error prevention review', 'assertion', 'x2-5-2-4-error-prevention-review'),
|
|
236
|
+
g('2.6.1', 'Avoid physical harm'),
|
|
237
|
+
p('2.6.1.1', 'No flashing over threshold', 'core', 'x2-6-1-1-no-flashing-over-threshold'),
|
|
238
|
+
p('2.6.1.2', 'No flashing over threshold (no exceptions)', 'supplemental', 'x2-6-1-2-no-flashing-over-threshold-no-exceptions'),
|
|
239
|
+
p('2.6.1.3', 'No visual motion', 'core', 'x2-6-1-3-no-visual-motion'),
|
|
240
|
+
p('2.6.1.4', 'No visual motion (no exceptions)', 'supplemental', 'x2-6-1-4-no-visual-motion-no-exceptions'),
|
|
241
|
+
p('2.6.1.5', 'Trigger warning available', 'core', 'x2-6-1-5-trigger-warning-available'),
|
|
242
|
+
p('2.6.1.6', 'Haptic stimulation adjustable', 'core', 'x2-6-1-6-haptic-stimulation-adjustable'),
|
|
243
|
+
p('2.6.1.7', 'Audio shifting adjustable', 'core', 'x2-6-1-7-audio-shifting-adjustable'),
|
|
244
|
+
p('2.6.1.8', 'Safe content review', 'assertion', 'x2-6-1-8-safe-content-review'),
|
|
245
|
+
g('2.7.1', 'Recognizable layouts'),
|
|
246
|
+
p('2.7.1.1', 'Conventional layout review', 'assertion', 'x2-7-1-1-conventional-layout-review'),
|
|
247
|
+
g('2.7.2', 'User orientation'),
|
|
248
|
+
p('2.7.2.1', 'Page/view title available', 'core', 'x2-7-2-1-page-view-title-available'),
|
|
249
|
+
p('2.7.2.2', 'Location within product review', 'assertion', 'x2-7-2-2-location-within-product-review'),
|
|
250
|
+
p('2.7.2.3', 'All steps listed', 'core', 'x2-7-2-3-all-steps-listed'),
|
|
251
|
+
p('2.7.2.4', 'Current step indicated', 'core', 'x2-7-2-4-current-step-indicated'),
|
|
252
|
+
p('2.7.2.5', 'Page/view change notified', 'core', 'x2-7-2-5-page-view-change-notified'),
|
|
253
|
+
p('2.7.2.6', 'Return to start supported', 'supplemental', 'x2-7-2-6-return-to-start-supported'),
|
|
254
|
+
g('2.7.3', 'Structure'),
|
|
255
|
+
p('2.7.3.1', 'Relationships detectable', 'supplemental', 'x2-7-3-1-relationships-detectable'),
|
|
256
|
+
p('2.7.3.2', 'Blocks of content available (minimum)', 'core', 'x2-7-3-2-blocks-of-content-available-minimum'),
|
|
257
|
+
p('2.7.3.3', 'Sections labeled', 'core', 'x2-7-3-3-sections-labeled'),
|
|
258
|
+
p('2.7.3.4', 'Heading structure available', 'supplemental', 'x2-7-3-4-heading-structure-available'),
|
|
259
|
+
p('2.7.3.5', 'Order detectable', 'core', 'x2-7-3-5-order-detectable'),
|
|
260
|
+
p('2.7.3.6', 'Blocks of content available (enhanced)', 'supplemental', 'x2-7-3-6-blocks-of-content-available-enhanced'),
|
|
261
|
+
p('2.7.3.7', 'Clear structure review', 'assertion', 'x2-7-3-7-clear-structure-review'),
|
|
262
|
+
p('2.7.3.8', 'Key information usability testing', 'assertion', 'x2-7-3-8-key-information-usability-testing'),
|
|
263
|
+
g('2.7.4', 'No obstruction'),
|
|
264
|
+
p('2.7.4.1', 'Overlay content dismissible', 'core', 'x2-7-4-1-overlay-content-dismissible'),
|
|
265
|
+
g('2.8.1', 'Consistency'),
|
|
266
|
+
p('2.8.1.1', 'Consistent structural order', 'supplemental', 'x2-8-1-1-consistent-structural-order'),
|
|
267
|
+
p('2.8.1.2', 'Consistent navigation order', 'supplemental', 'x2-8-1-2-consistent-navigation-order'),
|
|
268
|
+
p('2.8.1.3', 'Consistent navigation labels', 'supplemental', 'x2-8-1-3-consistent-navigation-labels'),
|
|
269
|
+
g('2.9.1', 'Avoid exclusionary cognitive tasks'),
|
|
270
|
+
p('2.9.1.1', 'Automated entry allowed', 'core', 'x2-9-1-1-automated-entry-allowed'),
|
|
271
|
+
p('2.9.1.2', 'Cognitive test alternatives available', 'core', 'x2-9-1-2-cognitive-test-alternatives-available'),
|
|
272
|
+
g('2.9.2', 'Adequate time'),
|
|
273
|
+
p('2.9.2.1', 'Timeout adjustable', 'supplemental', 'x2-9-2-1-timeout-adjustable'),
|
|
274
|
+
p('2.9.2.2', 'No time limits', 'supplemental', 'x2-9-2-2-no-time-limits'),
|
|
275
|
+
p('2.9.2.3', 'No unnecessary time limits', 'assertion', 'x2-9-2-3-no-unnecessary-time-limits'),
|
|
276
|
+
p('2.9.2.4', 'Time limits conveyed', 'supplemental', 'x2-9-2-4-time-limits-conveyed'),
|
|
277
|
+
g('2.9.3', 'Avoid deception'),
|
|
278
|
+
p('2.9.3.1', 'Preselections visible', 'core', 'x2-9-3-1-preselections-visible'),
|
|
279
|
+
p('2.9.3.2', 'Deceptive practices usability testing', 'assertion', 'x2-9-3-2-deceptive-practices-usability-testing'),
|
|
280
|
+
p('2.9.3.3', 'Deceptive messaging expert review', 'assertion', 'x2-9-3-3-deceptive-messaging-expert-review'),
|
|
281
|
+
g('2.9.4', 'Retain information'),
|
|
282
|
+
p('2.9.4.1', 'Going back supported', 'supplemental', 'x2-9-4-1-going-back-supported'),
|
|
283
|
+
p('2.9.4.2', 'No redundant entry', 'supplemental', 'x2-9-4-2-no-redundant-entry'),
|
|
284
|
+
p('2.9.4.3', 'Progress saved', 'supplemental', 'x2-9-4-3-progress-saved'),
|
|
285
|
+
g('2.9.5', 'Complete tasks'),
|
|
286
|
+
p('2.9.5.1', 'Required action available', 'supplemental', 'x2-9-5-1-required-action-available'),
|
|
287
|
+
p('2.9.5.2', 'Information requirements available at start', 'supplemental', 'x2-9-5-2-information-requirements-available-at-start'),
|
|
288
|
+
p('2.9.5.3', 'Process instructions available', 'supplemental', 'x2-9-5-3-process-instructions-available'),
|
|
289
|
+
g('2.9.6', 'Unnecessary steps'),
|
|
290
|
+
p('2.9.6.1', 'Usability testing for unnecessary steps', 'assertion', 'x2-9-6-1-usability-testing-for-unnecessary-steps'),
|
|
291
|
+
g('2.10.1', 'Risk'),
|
|
292
|
+
p('2.10.1.1', 'Consequences of choices explained', 'core', 'x2-10-1-1-consequences-of-choices-explained'),
|
|
293
|
+
p('2.10.1.2', 'Consequences explained before agreement', 'supplemental', 'x2-10-1-2-consequences-explained-before-agreement'),
|
|
294
|
+
p('2.10.1.3', 'Diverse disabilities considered', 'assertion', 'x2-10-1-3-diverse-disabilities-considered'),
|
|
295
|
+
p('2.10.1.4', 'Algorithm inclusivity review', 'assertion', 'x2-10-1-4-algorithm-inclusivity-review'),
|
|
296
|
+
g('2.10.2', 'Algorithms'),
|
|
297
|
+
p('2.10.2.1', 'Inclusive data set', 'assertion', 'x2-10-2-1-inclusive-data-set'),
|
|
298
|
+
p('2.10.2.2', 'No harm from algorithms', 'assertion', 'x2-10-2-2-no-harm-from-algorithms'),
|
|
299
|
+
g('2.11.1', 'Help available'),
|
|
300
|
+
p('2.11.1.1', 'Consistent help available', 'supplemental', 'x2-11-1-1-consistent-help-available'),
|
|
301
|
+
p('2.11.1.2', 'Contextual help available', 'supplemental', 'x2-11-1-2-contextual-help-available'),
|
|
302
|
+
p('2.11.1.3', 'Disabled controls explained', 'supplemental', 'x2-11-1-3-disabled-controls-explained'),
|
|
303
|
+
p('2.11.1.4', 'Sensory characteristics not relied on', 'core', 'x2-11-1-4-sensory-characteristics-not-relied-on'),
|
|
304
|
+
p('2.11.1.5', 'Supported decision-making review', 'assertion', 'x2-11-1-5-supported-decision-making-review'),
|
|
305
|
+
p('2.11.1.6', 'Help usability testing', 'assertion', 'x2-11-1-6-help-usability-testing'),
|
|
306
|
+
g('2.11.2', 'Feedback'),
|
|
307
|
+
p('2.11.2.1', 'Feedback mechanism available', 'supplemental', 'x2-11-2-1-feedback-mechanism-available'),
|
|
308
|
+
g('2.12.1', 'Assistive technology control'),
|
|
309
|
+
p('2.12.1.1', 'Assistive technology supported', 'core', 'x2-12-1-1-assistive-technology-supported'),
|
|
310
|
+
p('2.12.1.2', 'User settings supported', 'core', 'x2-12-1-2-user-settings-supported'),
|
|
311
|
+
p('2.12.1.3', 'Virtual cursor supported', 'core', 'x2-12-1-3-virtual-cursor-supported'),
|
|
312
|
+
p('2.12.1.4', 'Notifications adjustable', 'supplemental', 'x2-12-1-4-notifications-adjustable'),
|
|
313
|
+
g('2.12.3', 'Adjustable viewport'),
|
|
314
|
+
p('2.12.3.1', 'Orientation supported (minimum)', 'core', 'x2-12-3-1-orientation-supported-minimum'),
|
|
315
|
+
p('2.12.3.2', 'Orientation supported (enhanced)', 'supplemental', 'x2-12-3-2-orientation-supported-enhanced'),
|
|
316
|
+
p('2.12.3.3', 'Text reflow supported', 'supplemental', 'x2-12-3-3-text-reflow-supported'),
|
|
317
|
+
p('2.12.3.4', 'Layout reflow supported', 'supplemental', 'x2-12-3-4-layout-reflow-supported'),
|
|
318
|
+
g('2.12.4', 'Media control'),
|
|
319
|
+
p('2.12.4.1', 'Page/view audio adjustable', 'core', 'x2-12-4-1-page-view-audio-adjustable'),
|
|
320
|
+
p('2.12.4.2', 'Media alternatives searchable', 'supplemental', 'x2-12-4-2-media-alternatives-searchable'),
|
|
321
|
+
p('2.12.4.3', 'Media alternatives controllable', 'supplemental', 'x2-12-4-3-media-alternatives-controllable'),
|
|
322
|
+
p('2.12.4.4', 'Media chapters available', 'supplemental', 'x2-12-4-4-media-chapters-available'),
|
|
323
|
+
g('2.12.5', 'Content changes'),
|
|
324
|
+
p('2.12.5.1', 'Change of content notified', 'core', 'x2-12-5-1-change-of-content-notified'),
|
|
325
|
+
p('2.12.5.2', 'Change of focus notified', 'core', 'x2-12-5-2-change-of-focus-notified'),
|
|
326
|
+
p('2.12.5.3', 'Change of user agent notified', 'core', 'x2-12-5-3-change-of-user-agent-notified'),
|
|
327
|
+
];
|
|
328
|
+
|
|
329
|
+
function g(num, name) { return { guideline: true, num, name }; }
|
|
330
|
+
function p(num, name, type, anchor) { return { num, name, type, anchor }; }
|
|
331
|
+
|
|
332
|
+
export const provisions = (() => {
|
|
333
|
+
const list = [];
|
|
334
|
+
let guideline = null;
|
|
335
|
+
for (const entry of RAW) {
|
|
336
|
+
if (entry.guideline) { guideline = entry; continue; }
|
|
337
|
+
list.push({
|
|
338
|
+
num: entry.num,
|
|
339
|
+
name: entry.name,
|
|
340
|
+
type: entry.type,
|
|
341
|
+
guideline: `${guideline.num} ${guideline.name}`,
|
|
342
|
+
url: `${draftInfo.url}#${entry.anchor}`,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
return list;
|
|
346
|
+
})();
|
|
347
|
+
|
|
348
|
+
const provisionByNum = new Map(provisions.map((entry) => [entry.num, entry]));
|
|
349
|
+
|
|
350
|
+
// Rule id → the draft requirements its failure also fails.
|
|
351
|
+
export const RULE_REQUIREMENTS = {
|
|
352
|
+
'area-alt': ['2.1.1.3', '2.3.5.2'],
|
|
353
|
+
'canvas-alt': ['2.1.3.1'],
|
|
354
|
+
'embed-alt': ['2.1.3.1'],
|
|
355
|
+
'image-alt': ['2.1.1.3'],
|
|
356
|
+
'input-image-alt': ['2.1.1.3', '2.3.5.2'],
|
|
357
|
+
'object-alt': ['2.1.3.1'],
|
|
358
|
+
'svg-img-alt': ['2.1.1.3'],
|
|
359
|
+
'audio-transcript': ['2.1.4.2'],
|
|
360
|
+
'media-captions': ['2.1.5.2'],
|
|
361
|
+
'video-audio-description': ['2.1.6.1'],
|
|
362
|
+
'aria-required-children': ['2.3.5.6'],
|
|
363
|
+
'aria-required-parent': ['2.3.5.6'],
|
|
364
|
+
'definition-list': ['2.7.3.1'],
|
|
365
|
+
'dlitem-parent': ['2.7.3.1'],
|
|
366
|
+
'form-label': ['2.3.5.2'],
|
|
367
|
+
'list-structure': ['2.7.3.1'],
|
|
368
|
+
'listitem-parent': ['2.7.3.1'],
|
|
369
|
+
'p-as-heading': ['2.7.3.4'],
|
|
370
|
+
'table-headers': ['2.7.3.1'],
|
|
371
|
+
// A lock to the platform's own default orientation meets 2.12.3.1; only
|
|
372
|
+
// the supplemental both-orientations requirement fails for every lock.
|
|
373
|
+
'orientation-lock': ['2.12.3.2'],
|
|
374
|
+
'region-purpose': ['2.7.3.3'],
|
|
375
|
+
'link-in-text-block': ['2.1.8.1'],
|
|
376
|
+
'audio-control': ['2.12.4.1'],
|
|
377
|
+
'color-contrast': ['2.2.1.3'],
|
|
378
|
+
'control-contrast': ['2.2.1.3'],
|
|
379
|
+
'meta-viewport': ['2.2.1.6'],
|
|
380
|
+
'color-contrast-enhanced': ['2.2.1.10'],
|
|
381
|
+
'reflow': ['2.12.3.4'],
|
|
382
|
+
'non-text-contrast': ['2.3.5.1'],
|
|
383
|
+
'scrollable-region-focusable': ['2.4.1.2'],
|
|
384
|
+
'meta-refresh': ['2.9.2.1'],
|
|
385
|
+
'pause-stop-hide': ['2.6.1.3'],
|
|
386
|
+
'video-loop-motion': ['2.6.1.3'],
|
|
387
|
+
'meta-refresh-no-exceptions': ['2.9.2.2'],
|
|
388
|
+
'reduced-motion': ['2.12.1.2'],
|
|
389
|
+
'bypass-blocks': ['2.7.3.2'],
|
|
390
|
+
'document-title': ['2.7.2.1'],
|
|
391
|
+
'visual-order-divergence': ['2.3.3.3'],
|
|
392
|
+
'link-name': ['2.3.5.2'],
|
|
393
|
+
'link-text-generic': ['2.3.5.2'],
|
|
394
|
+
'heading-label-placeholder': ['2.7.3.3', '2.3.5.2'],
|
|
395
|
+
'focus-visible': ['2.3.1.2'],
|
|
396
|
+
'section-heading': ['2.7.3.4'],
|
|
397
|
+
// Path-based gestures (2.4.5.4) and multipoint ones (2.4.3.2 lists them
|
|
398
|
+
// among complex pointer inputs).
|
|
399
|
+
'pointer-gesture-alternative': ['2.4.5.4', '2.4.3.2'],
|
|
400
|
+
'drag-alternative': ['2.4.3.2'],
|
|
401
|
+
'html-lang': ['2.2.2.2'],
|
|
402
|
+
'valid-lang-parts': ['2.2.2.2'],
|
|
403
|
+
'error-message-linkage': ['2.5.1.5'],
|
|
404
|
+
'financial-form-confirmation': ['2.5.2.1'],
|
|
405
|
+
'redundant-entry': ['2.9.4.2'],
|
|
406
|
+
'auth-field-obstruction': ['2.9.1.1'],
|
|
407
|
+
'captcha-alternative': ['2.9.1.2'],
|
|
408
|
+
'aria-allowed-attr': ['2.3.5.6'],
|
|
409
|
+
'aria-attr-valid': ['2.3.5.6'],
|
|
410
|
+
'aria-field-name': ['2.3.5.2'],
|
|
411
|
+
'aria-hidden-focus': ['2.3.5.6'],
|
|
412
|
+
'aria-state-unreachable': ['2.3.5.6'],
|
|
413
|
+
'aria-valid-refs': ['2.3.5.6'],
|
|
414
|
+
'button-name': ['2.3.5.2'],
|
|
415
|
+
'composite-widget-name': ['2.3.5.2'],
|
|
416
|
+
'frame-title': ['2.7.3.3'],
|
|
417
|
+
'invoker-target': ['2.3.5.6'],
|
|
418
|
+
'label-for-valid': ['2.3.5.2'],
|
|
419
|
+
'nested-interactive': ['2.3.5.6'],
|
|
420
|
+
'role-required-aria': ['2.3.5.6'],
|
|
421
|
+
'summary-name': ['2.3.5.2'],
|
|
422
|
+
'valid-role': ['2.3.5.6'],
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// WCAG rules that stay out of a WCAG 3 audit, and why. Best-practice rules
|
|
426
|
+
// are not listed: they run in either standard when best practices are on.
|
|
427
|
+
export const UNMAPPED_RULES = {
|
|
428
|
+
'reading-order-divergence': 'The draft has no requirement on reading sequence; focus order (2.3.3.3) is a different outcome.',
|
|
429
|
+
'autocomplete-valid': 'An invalid token hinders autofill but does not prevent automated entry (2.9.1.1), and the draft has no identify-purpose requirement.',
|
|
430
|
+
'text-justified': 'The draft asks that justification can be adjusted (2.2.1.4), not that text is unjustified by default.',
|
|
431
|
+
'text-spacing': 'The rule applies letter and word spacing, which neither adjustable-text requirement (2.2.1.4, 2.2.1.5) lists, so a clipping it finds may not fail the draft.',
|
|
432
|
+
'aria-label-misuse': 'It concerns labels on plain containers; the draft asks for names on interactive elements (2.3.5.2, 2.3.5.6) and on meaningful blocks only (2.7.3.3).',
|
|
433
|
+
'link-text-generic-only': 'The draft does not say whether a name may take its purpose from context; the context-aware link-text-generic rule stands for 2.3.5.2.',
|
|
434
|
+
'focus-not-obscured': 'The draft has no focus-obscured requirement.',
|
|
435
|
+
'focus-not-obscured-enhanced': 'The draft has no focus-obscured requirement.',
|
|
436
|
+
'target-size': 'The draft has no target size requirement.',
|
|
437
|
+
'target-size-enhanced': 'The draft has no target size requirement.',
|
|
438
|
+
'on-input-navigation': 'The draft\'s change notifications (2.7.2.5, 2.12.5) cover different triggers from a change of context on input.',
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
/** The check set a value names, 'core' or 'supplemental', or null. */
|
|
442
|
+
export function checkSetOf(value) {
|
|
443
|
+
const name = String(value ?? '').toLowerCase();
|
|
444
|
+
return CHECK_SETS.includes(name) ? name : RENAMED_SETS[name] ?? null;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** The WCAG 3 check set a tag list asks for, wcag3-core or
|
|
448
|
+
* wcag3-supplemental, the larger when both are present; null for a
|
|
449
|
+
* WCAG 2 audit. */
|
|
450
|
+
export function checkSetFromTags(tags) {
|
|
451
|
+
let found = null;
|
|
452
|
+
for (const tag of tags ?? []) {
|
|
453
|
+
const set = /^wcag3-/.test(tag) ? checkSetOf(tag.slice(6)) : null;
|
|
454
|
+
if (set && (!found || CHECK_SETS.indexOf(set) > CHECK_SETS.indexOf(found))) found = set;
|
|
455
|
+
}
|
|
456
|
+
return found;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** The draft requirements a rule speaks to, as { num, name, type, url }. */
|
|
460
|
+
export function requirementsForRule(ruleId) {
|
|
461
|
+
return (RULE_REQUIREMENTS[ruleId] ?? []).map((num) => {
|
|
462
|
+
const { name, type, url } = provisionByNum.get(num);
|
|
463
|
+
return { num, name, type: TYPE_LABELS[type], url };
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** Does the check set take in any requirement this rule tests? */
|
|
468
|
+
export function ruleInCheckSet(ruleId, set) {
|
|
469
|
+
const types = SET_TYPES[set];
|
|
470
|
+
return Boolean(types) && (RULE_REQUIREMENTS[ruleId] ?? [])
|
|
471
|
+
.some((num) => types.has(provisionByNum.get(num).type));
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Rule selection shared by every runner. A tag list selects the rules that
|
|
476
|
+
* carry any of its tags; a WCAG 3 check set tag also selects the rules
|
|
477
|
+
* matched to its requirements. An empty or absent list selects every rule.
|
|
478
|
+
*/
|
|
479
|
+
export function ruleMatchesTags(rule, tags) {
|
|
480
|
+
if (!tags?.length) return true;
|
|
481
|
+
if (rule.tags.some((tag) => tags.includes(tag))) return true;
|
|
482
|
+
const set = checkSetFromTags(tags);
|
|
483
|
+
return Boolean(set) && ruleInCheckSet(rule.id, set);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* The check set's requirements as a human checklist, shaped like the WCAG 2
|
|
488
|
+
* manual-review entries: `level` carries the provision type and `principle`
|
|
489
|
+
* the guideline. Every requirement in the set is listed, since no rule was
|
|
490
|
+
* written for any of them; the ones a rule speaks to are 'partial'.
|
|
491
|
+
* Assertions are in neither set: they are not needed to conform, and only
|
|
492
|
+
* the people who make the content can make them.
|
|
493
|
+
*/
|
|
494
|
+
export function manualReviewChecklist(set) {
|
|
495
|
+
const types = SET_TYPES[set];
|
|
496
|
+
if (!types) return [];
|
|
497
|
+
const partly = new Set(Object.values(RULE_REQUIREMENTS).flat());
|
|
498
|
+
return provisions
|
|
499
|
+
.filter((entry) => types.has(entry.type))
|
|
500
|
+
.map(({ num, name, type, guideline, url }) => ({
|
|
501
|
+
num,
|
|
502
|
+
name,
|
|
503
|
+
level: TYPE_LABELS[type],
|
|
504
|
+
principle: guideline,
|
|
505
|
+
url,
|
|
506
|
+
automation: partly.has(num) ? 'partial' : 'manual',
|
|
507
|
+
draft: true,
|
|
508
|
+
}));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** The results header for a WCAG 3 audit: the draft, the check set and
|
|
512
|
+
* what it covers, as "core requirements". */
|
|
513
|
+
export function standardFor(set) {
|
|
514
|
+
return { ...draftInfo, checkSet: set, scope: SET_SCOPES[set] };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export default {
|
|
518
|
+
draftInfo, CHECK_SETS, provisions, RULE_REQUIREMENTS, UNMAPPED_RULES,
|
|
519
|
+
checkSetOf, checkSetFromTags, requirementsForRule, ruleInCheckSet, ruleMatchesTags, manualReviewChecklist, standardFor,
|
|
520
|
+
};
|
package/package.json
CHANGED