Web Accessibility (WCAG 2.2) Module 2 – WCAG 2.2: how the standard works
Reading success criteria like an auditor
Read WCAG 2.2 criteria the way auditors do: defined terms, conditions and exceptions, not applicable, techniques and failures, and a verdict you can defend.
What you will learn
- Apply WCAG's defined terms such as programmatically determined, CSS pixel and user interface component
- Use conditions and exceptions to reach a pass, fail or not-applicable verdict
- Find sufficient techniques, advisory techniques and documented failures for a criterion
- Justify a verdict in two sentences that quote the criterion
Before you start
What you’ll learn · Before you start
What you will learn
- Apply WCAG's defined terms such as programmatically determined, CSS pixel and user interface component
- Use conditions and exceptions to reach a pass, fail or not-applicable verdict
- Find sufficient techniques, advisory techniques and documented failures for a criterion
- Justify a verdict in two sentences that quote the criterion
Before you start
On this page
The same grey, #888888 on white, passes WCAG’s contrast criterion as 24 px text and fails it as 18 px bold text. The colour never changes: the verdict turns on how WCAG’s glossary defines large-scale text, and many disputes between auditors come down to a definition, a condition or an exception that one of them skipped.
You will be able to apply WCAG’s defined terms, read a criterion’s conditions and exceptions as logic, use not applicable correctly, find the techniques and documented failures for a criterion, and write a verdict that quotes the criterion and survives a second opinion.
The glossary decides what a criterion means
1.4.3 Contrast (Minimum) asks for “a contrast ratio of at least 4.5:1”, except that large-scale text needs 3:1, and text that is incidental or part of a logo has no requirement (Understanding 1.4.3). Three of those words are defined in WCAG’s glossary, and the definitions are part of the requirement. A WCAG's measure of the difference in brightness between two colours, from 1:1 for the same colour to 21:1 for black on white, worked out from their relative luminance with the formula in WCAG's glossary. comes from a formula, and large-scale text: In WCAG, text of at least 18 point, or at least 14 point if bold: 24 CSS px and about 18.67 CSS px bold, since CSS sets 1pt at 4/3 px. It needs a contrast ratio of 3:1 under 1.4.3 instead of 4.5:1. means at least 18 point, or 14 point bold. The program works out eight verdicts from those definitions alone:
// Verdicts for 1.4.3 Contrast (Minimum), worked from WCAG 2.2's own definitions: relative
// luminance and contrast ratio from the glossary, "large scale" text as at least 18 point, or
// 14 point bold, and the criterion's exceptions for incidental text and logotypes. CSS defines
// 1pt as 4/3 px, so 18pt is 24 px and 14pt is 18.67 px. Ratios are compared without rounding.
function channel(hex) {
const c = parseInt(hex, 16) / 255;
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
}
function luminance(colour) {
const [r, g, b] = [1, 3, 5].map((i) => channel(colour.slice(i, i + 2)));
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
function contrast(a, b) {
const [light, dark] = [luminance(a), luminance(b)].sort((x, y) => y - x);
return (light + 0.05) / (dark + 0.05);
}
const cases = [
{ text: 'Price, 16 px', fg: '#888888', bg: '#ffffff', px: 16, bold: false },
{ text: 'Heading, 24 px', fg: '#888888', bg: '#ffffff', px: 24, bold: false },
{ text: 'Label, 18 px bold', fg: '#888888', bg: '#ffffff', px: 18, bold: true },
{ text: 'Label, 19 px bold', fg: '#888888', bg: '#ffffff', px: 19, bold: true },
{ text: 'Body, 16 px', fg: '#767676', bg: '#ffffff', px: 16, bold: false },
{ text: 'Body, 16 px', fg: '#777777', bg: '#ffffff', px: 16, bold: false },
{ text: 'Disabled button', fg: '#aaaaaa', bg: '#ffffff', px: 16, bold: false, inactive: true },
{ text: 'Logo wordmark', fg: '#cccccc', bg: '#ffffff', px: 32, bold: true, logo: true },
];
console.log('Text Colour Ratio Large Needs Verdict');
for (const c of cases) {
const ratio = contrast(c.fg, c.bg);
const pt = c.px * 0.75;
const large = pt >= 18 || (c.bold && pt >= 14);
const needs = large ? 3 : 4.5;
let verdict;
if (c.inactive) verdict = 'pass: inactive control, no requirement';
else if (c.logo) verdict = 'pass: logotype, no requirement';
else verdict = ratio >= needs ? 'pass' : 'fail';
const shown = `${ratio.toFixed(3)}:1`;
console.log(`${c.text.padEnd(19)}${c.fg.padEnd(10)}${shown.padEnd(8)}${(large ? 'yes' : 'no').padEnd(7)}${`${needs}:1`.padEnd(7)}${verdict}`);
} Output
Text Colour Ratio Large Needs Verdict Price, 16 px #888888 3.545:1 no 4.5:1 fail Heading, 24 px #888888 3.545:1 yes 3:1 pass Label, 18 px bold #888888 3.545:1 no 4.5:1 fail Label, 19 px bold #888888 3.545:1 yes 3:1 pass Body, 16 px #767676 4.542:1 no 4.5:1 pass Body, 16 px #777777 4.478:1 no 4.5:1 fail Disabled button #aaaaaa 2.323:1 no 4.5:1 pass: inactive control, no requirement Logo wordmark #cccccc 1.606:1 yes 3:1 pass: logotype, no requirement
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node contrast_verdicts.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
The first four rows are the same grey at 3.545:1. At 16 px it fails, at 24 px it passes, because 24 px is 18 point. At 18 px bold it fails, because 18 px is only 13.5 point, and at 19 px bold it passes. The next two rows show why auditors never round: #767676 gives 4.542:1 and passes, #777777 gives 4.478:1 and fails, and the Understanding page is explicit that “4.499:1 would not meet the 4.5:1 threshold”. The last two pass by exception, not by contrast.
Rounding the size threshold
Wrong: “14 point is 18.5 px, so 18.5 px bold text is large.” The Understanding page gives 18.5 px as an approximation, but CSS defines a point as 4/3 of a pixel, so 14 point is 18.67 px. Report text between the two, such as 18.6 px bold, with both numbers rather than picking the one that passes.
Contrast is the easy case, because its definitions are numbers. Other defined terms decide verdicts in the same way:
| Defined term | What WCAG means by it | Where it decides |
|---|---|---|
| programmatically determined: Able to be read by software from what the author provided, so that browsers and assistive technology can present it in other forms, such as speech or braille. A heading marked up as a heading is; bold text that only looks like one is not. | Software can read it from what the author provided | 1.3.1: bold text that looks like a heading but is a plain paragraph fails |
| CSS pixel: The unit WCAG uses for sizes such as 24 by 24 targets: a visual angle of about 0.0213 degrees, the px of CSS. It is independent of the screen's physical pixels, so zoom and screen density do not change the count. | A unit of visual angle, about 0.0213 degrees | 2.5.8: a target is measured in CSS px, not in the phone screen’s pixels |
| user interface component: In WCAG, a part of the content that users perceive as a single control for a distinct function, such as a button, a link, a checkbox or a custom switch, even when it is built from several elements. | What users perceive as a single control | 1.4.11 and 4.1.2: a custom switch is one component, with one name |
| Keyboard interface | The interface software uses to receive keystrokes | 2.1.1: a mouse emulator such as MouseKeys does not count |
| Satisfies a success criterion | The criterion does not evaluate to false on the page | Every criterion: nothing to judge means satisfied |
Checkpoint
Check: Defined terms decide verdicts
Conditions and exceptions read as logic
Success criteria are written in a small number of patterns, and each pattern is a piece of logic. “For each” scopes the test: every instance is judged on its own. “At least one of the following is true” is an OR across a list. A list item with several parts joined by “and” is an AND: every part must hold. “Except” names cases where the criterion asks nothing. Numbers are thresholds, compared exactly.
2.2.1 Timing Adjustable uses almost all of them. For each time limit that the content sets, at least one of six options must be true: the user can turn the limit off; adjust it beforehand to at least ten times the default; or extend it after a warning, with at least 20 seconds to act, by a simple action, at least ten times. Three exceptions follow: a limit that is a required part of a real-time event, a limit that is essential, and a limit longer than 20 hours (Understanding 2.2.1). Read it option by option on two time limits:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- A bank's session. A bank signs users out after 10 minutes without activity, warns 30 seconds before and offers Stay signed in once. 2.2.1 applies to each time limit the content sets, and asks that at least one of six options is true.
- Turn off?. The user has no way to turn the limit off before meeting it, so the first option is false.
- Adjust?. Nothing lets the user choose a longer limit in advance, so the second option is false too.
- Extend?. There is a warning, 30 seconds is more than the 20 the option asks for, and one button is a simple action. But the option also says the user may extend at least ten times, and this site allows one.
- The exceptions. Banking is not a real-time event, nothing is spoiled by more time, and 10 minutes is far under 20 hours. All three exceptions are false.
- The verdict. None of the six is true, so the session limit fails 2.2.1. Allowing the extension at least ten times would make the extend option true, and the limit would pass.
- A seat hold. A ticket site holds chosen seats for two minutes while the buyer confirms, then returns them to sale. Understanding 2.2.1 gives this as an example of the essential exception, so this limit passes.
- When it does not apply. A limit set by the browser rather than the content is outside the criterion, and a page with no time limit satisfies it. Auditors record that case as not applicable, with the reason.
The bank failed on one word of the extend option, “ten”, although it had a warning, a simple action and more than 20 seconds. A quick reading that stops at “there is a warning and a button” passes the session, and the auditor who reads the whole option fails it. The seat hold passed through an exception that the Understanding page itself uses as its example, “two minutes to confirm a purchase”, after which the seats go back on sale.
Key idea
Read every criterion as logic: scope it with “for each”, join with OR and AND exactly as written, test every number without rounding, and look for the exceptions before you fail anything.
Checkpoint
Check: Conditions and exceptions as logic
Not applicable means satisfied, and the report says why
A page with no video still has a verdict for 1.2.2 Captions (Prerecorded). WCAG’s definition says a page satisfies a criterion when the criterion “does not evaluate to ‘false’” on it, and the Understanding page on conformance spells out the consequence: when no content on the page is of the kind a criterion covers, that criterion “is satisfied” (Understanding Conformance). For the conformance decision, then, not applicable counts as a pass.
Reports still keep the two apart, because a reader needs to know what was there to test. W3C’s evaluation methodology, WCAG-EM, lets a report mark such criteria as not present (WCAG-EM 2.0), and conformance reports commonly say not applicable. Either way, the line names the reason:
| Page | Criterion | Verdict | Reason |
|---|---|---|---|
| Contact form | 1.2.2 Captions (Prerecorded) | Not applicable | No prerecorded audio or video on the page |
| Contact form | 2.2.1 Timing Adjustable | Not applicable | The page sets no time limit |
| Product page | 1.2.2 Captions (Prerecorded) | Fail | The demo video has speech and no captions |
| Product page | 2.5.7 Dragging Movements | Pass | The image carousel also moves with its arrow buttons |
Not applicable is a finding about the page, so it needs the same care as a fail. Mark a criterion not applicable only after looking for its content: a page whose video loads only after a click on Play still has a video.
Checkpoint
Check: Not applicable and satisfied
Techniques show ways to pass; failures show ways to fail
Every Understanding page lists its techniques near its end, and W3C’s quick reference, How to Meet WCAG, lists the same techniques for every criterion with filters by level and technology. Each technique has an identifier whose prefix says what it is about: G for general, H for HTML, C for CSS, ARIA, SCR for client-side script, SVR for server-side script, SM for SMIL, T for plain text, PDF, and F for the common failures (Techniques for WCAG 2.2). For the two criteria of this lesson:
| Kind | 1.4.3 Contrast (Minimum) | 2.2.1 Timing Adjustable |
|---|---|---|
| Sufficient | G18 4.5:1 for normal text; G145 3:1 for large text; G148 the browser’s default colours; G174 a high-contrast switch | G198 a way to turn the limit off; G180 a setting of ten times the default; SCR16 with SCR1 a warning and an extension; four more for session forms and moving text |
| Advisory | G156 a technology whose browsers let users change text colours | (none listed on this page) |
| Failures | F24 a text colour set without a background colour, or the reverse; F83 a background image under the text | F40 a timed meta redirect; F41 a meta refresh that reloads the page; F58 a server-side redirect after a timeout |
The kinds carry different weight. A sufficient technique done correctly is good evidence of a pass, and content may still pass in ways no technique describes. Advisory techniques improve accessibility without counting as sufficient. A documented failure is the strongest evidence of all, because content that matches one does not meet the criterion, so an auditor checks the failures first (Understanding Techniques).
Color Contrast Checker (WCAG 2.2) Check a colour pair of your own against the 4.5:1 and 3:1 thresholds, then decide whether the text is large scale.Checkpoint
Check: Techniques and documented failures
A verdict quotes the criterion and the evidence
A verdict that only says “fail” invites an argument. A defensible verdict is two sentences: the first gives the result, the criterion and the instance; the second gives the evidence and the words of the criterion that decide. A developer can then fix it, and another auditor can check it without asking you anything.
| Weak | Defensible |
|---|---|
| Contrast fails. | Fails 1.4.3 Contrast (Minimum): the price text, 16 px regular in #888888 on white, measures 3.545:1. Text that is not large scale needs “at least 4.5:1”. |
| The timeout is fine, there is a warning. | Fails 2.2.1 Timing Adjustable: the 10-minute session warns 30 seconds before and allows one extension. The extend option requires extensions “at least ten times”. |
| N/A. | Not applicable to 1.2.2 Captions (Prerecorded): the contact page has no prerecorded audio or video. With no content to which it applies, the criterion is satisfied. |
Each defensible verdict names one instance, gives measurements or behaviour anyone can repeat, and quotes only the words that decide, briefly. That last habit matters most in disputes: when two auditors disagree, the quoted words show which condition or exception one of them missed.
Checkpoint
Check: Writing a defensible verdict
Practise: reach a verdict on 2.2.1 in code
The exercise asks you to write 2.2.1 as code: the six options in the criterion’s order, the extend option with all four of its parts, and not applicable for limits the content does not set. Its sample tests include the bank’s session and the seat hold:
Exercise · Easy · JavaScript
Reach a verdict on 2.2.1 Timing Adjustable for a time limit
2.2.1 Timing Adjustable, Level A, applies to each time limit that the content sets, and asks that at least one of six options is true. Write timingVerdict(limit) in timing.mjs so that it reads a time limit the way an auditor reads the criterion.
limit has these fields:
setByContent:truewhen the content sets the limit,falsewhen something else, such as the browser, does.canTurnOff: the user can turn the limit off before meeting it.adjustUpTo: how many times the default length the user can set it to in advance (1when they cannot change it).warnSeconds: how many seconds the user gets after a warning to extend it, ornullwhen there is no warning.simpleAction: extending takes a simple action, such as pressing one button.extensions: how many times the user may extend it.realTime: the limit is a required part of a real-time event, such as an auction.essential: the limit is essential, and extending it would spoil the activity.hours: the length of the limit in hours.
Return the first option that is true, in the criterion's order, as one of 'pass:turn-off', 'pass:adjust' (the user can set at least ten times the default), 'pass:extend' (a warning, at least 20 seconds to act, a simple action and at least ten extensions, all four), 'pass:real-time', 'pass:essential' and 'pass:20-hour' (longer than 20 hours). Return 'fail' when none is true, and 'not-applicable' when the content does not set the limit.
The sample tests import timingVerdict from timing.mjs and run in your browser.
Starter code · timing.mjs
/** The verdict on 2.2.1 Timing Adjustable for one time limit. */
export function timingVerdict(limit) {
if (!limit.setByContent) return 'not-applicable';
if (limit.canTurnOff) return 'pass:turn-off';
// Check the other five options here, in the criterion's order.
return 'fail';
} The sample tests · timing.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { timingVerdict } from './timing.mjs';
// A time limit with every option false; each test switches on what it needs.
const limit = (extra = {}) => ({
setByContent: true, canTurnOff: false, adjustUpTo: 1, warnSeconds: null, simpleAction: false,
extensions: 0, realTime: false, essential: false, hours: 0.25, ...extra,
});
test("the lesson's bank session, one extension after a 30 second warning, fails", () => {
assert.equal(timingVerdict(limit({ hours: 10 / 60, warnSeconds: 30, simpleAction: true, extensions: 1 })), 'fail');
});
test('the same session with ten extensions passes by the extend option', () => {
assert.equal(timingVerdict(limit({ warnSeconds: 30, simpleAction: true, extensions: 10 })), 'pass:extend');
});
test('extend needs all four parts: 20 seconds, a simple action and ten extensions after a warning', () => {
assert.equal(timingVerdict(limit({ warnSeconds: 19, simpleAction: true, extensions: 10 })), 'fail');
assert.equal(timingVerdict(limit({ warnSeconds: 20, simpleAction: false, extensions: 10 })), 'fail');
assert.equal(timingVerdict(limit({ warnSeconds: null, simpleAction: true, extensions: 10 })), 'fail');
assert.equal(timingVerdict(limit({ warnSeconds: 20, simpleAction: true, extensions: 10 })), 'pass:extend');
});
test('adjusting to ten times the default passes, nine times does not', () => {
assert.equal(timingVerdict(limit({ adjustUpTo: 10 })), 'pass:adjust');
assert.equal(timingVerdict(limit({ adjustUpTo: 9 })), 'fail');
});
test('the seat hold passes by the essential exception, an auction by the real-time one', () => {
assert.equal(timingVerdict(limit({ hours: 2 / 60, essential: true })), 'pass:essential');
assert.equal(timingVerdict(limit({ realTime: true })), 'pass:real-time');
});
test('the 20 hour exception needs more than 20 hours', () => {
assert.equal(timingVerdict(limit({ hours: 24 })), 'pass:20-hour');
assert.equal(timingVerdict(limit({ hours: 20 })), 'fail');
});
test('the first true option in the criterion order is the one reported', () => {
assert.equal(timingVerdict(limit({ canTurnOff: true, essential: true })), 'pass:turn-off');
assert.equal(timingVerdict(limit({ adjustUpTo: 20, realTime: true })), 'pass:adjust');
});
test('a limit the content does not set is not applicable', () => {
assert.equal(timingVerdict(limit({ setByContent: false })), 'not-applicable');
}); A hint
Check the options as a list of if statements in the order the criterion gives them, returning as soon as one is true. The extend option is the only one with several parts: write it as one condition joined with &&, and remember that warnSeconds can be null.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Then test your reading of all five ideas, from the glossary to the verdict; the questions follow your answers:
Practice that adapts to you
Practise this lesson
11 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Fresher · What does “programmatically determined” mean in WCAG, and why does it matter?
It means software can work the information out from what the author provided, so that browsers and assistive technology can present it in other ways, such as speech or braille. WCAG uses it in criteria such as 1.3.1 Info and Relationships: a heading must be marked up as a heading, not just made big and bold, because a screen reader cannot tell that bold text is a heading, and so it cannot list it or let a user jump to it. In short, what is shown visually must also be in the markup, where software can read it.
Two harder questions of this module start from this lesson:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A banking site warns users 30 seconds before a 10-minute session ends and offers one "Stay signed in" button, once. A colleague marks 2.2.1 Timing Adjustable as a pass. Do you agree? Write the verdict, and say how the bank could pass without weakening its security.
-
Fresher to mid
Two testers disagree about a page with no audio or video. One marks the 1.2 criteria "Not applicable", the other "Pass". Who is right, does it change the conformance result, and what should the report say?
Key takeaways
- WCAG’s glossary is part of the requirement: large-scale text, contrast ratio, CSS pixel and the other defined terms decide verdicts, and thresholds are compared without rounding.
- Read each criterion as logic, “for each”, OR, AND and “except”, and look for the exceptions before failing anything.
- A criterion with nothing to judge on a page is satisfied; reports still say not applicable, with the reason.
- Sufficient techniques show ways to pass and documented failures ways to fail; failures are the first thing to check.
- A defensible verdict is two sentences: the result, the criterion and the instance, then the evidence with the criterion’s deciding words.
Terms in this lesson
- CSS pixel
- The unit WCAG uses for sizes such as 24 by 24 targets: a visual angle of about 0.0213 degrees, the px of CSS. It is independent of the screen's physical pixels, so zoom and screen density do not change the count.
- large-scale text
- In WCAG, text of at least 18 point, or at least 14 point if bold: 24 CSS px and about 18.67 CSS px bold, since CSS sets 1pt at 4/3 px. It needs a contrast ratio of 3:1 under 1.4.3 instead of 4.5:1.
- programmatically determined
- Able to be read by software from what the author provided, so that browsers and assistive technology can present it in other forms, such as speech or braille. A heading marked up as a heading is; bold text that only looks like one is not.
- user interface component
- In WCAG, a part of the content that users perceive as a single control for a distinct function, such as a button, a link, a checkbox or a custom switch, even when it is built from several elements.
References
- WCAG 2.2: Glossary (W3C)
- WCAG 2.2: 1.4.3 Contrast (Minimum) (W3C)
- Understanding SC 1.4.3: Contrast (Minimum) (W3C Web Accessibility Initiative)
- Understanding SC 2.2.1: Timing Adjustable (W3C Web Accessibility Initiative)
- Understanding Conformance (W3C Web Accessibility Initiative)
- Techniques for WCAG 2.2 (W3C Web Accessibility Initiative)
- How to Meet WCAG (Quick Reference) (W3C Web Accessibility Initiative)
- WCAG-EM 2.0: Website Accessibility Conformance Evaluation Methodology (W3C)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress