Volunteer shifts
Saturday
Two places left.
{ "pack_id": "accessibility-audit", "title": "Accessibility audit", "version": "0.1.0", "summary": "A collection of nouns and verbs for auditing a web page or app against WCAG 2.2 level AA: keyboard paths, focus order and visibility, contrast, text alternatives, captions and transcripts, reflow at 320 CSS pixels, target size, reduced motion, language and landmarks, with every finding citing its success criterion, and tested code for contrast ratios and landmark and heading order.", "boundaries": [ "Every finding cites one WCAG 2.2 success criterion and says failure or advisory; AAA criteria are advisory in an AA audit.", "Captures and keyboard walks are read-only: no form is submitted, nothing is typed into fields and no account is created.", "A report never claims conformance; it states what was checked and what was not.", "A person's assistive-technology session is a person's verb; the audit review is by a different worker.", "The pack's code reads files it is given and prints results; it opens no network connection and calls no model.", "Workers create nouns and propose verbs; an authorized requester enqueues the proposals worth doing.", "Importing the pack writes nothing, enqueues nothing and installs no code." ], "nouns": [ { "slug": "pack", "category": "noun.skill_pack", "role": "pack", "title": "A collection of nouns and verbs for auditing a page or app against WCAG 2.2 level AA", "value": { "lets_workers": "Plan an audit across pages, states, widths, themes and input modes; capture each state read-only; check contrast, structure, keyboard paths, reflow, target size, media, motion and text alternatives; have a person try the key tasks with assistive technology; and write a report whose every finding cites its WCAG success criterion, says failure or advisory and states what was not checked.", "flow": [ { "verb": { "$verb": "accessibility-audit.plan-audit" }, "when": "Before checking anything." }, { "verb": { "$verb": "accessibility-audit.capture-states" }, "when": "Once per plan; deterministic and read-only." }, { "verb": { "$verb": "accessibility-audit.check-contrast" }, "when": "On the captures, in every theme; deterministic." }, { "verb": { "$verb": "accessibility-audit.lint-structure" }, "when": "On the rendered DOM snapshots; deterministic." }, { "verb": { "$verb": "accessibility-audit.walk-keyboard" }, "when": "On the live pages, keyboard only." }, { "verb": { "$verb": "accessibility-audit.check-reflow" }, "when": "At 320 CSS pixels and with wider text spacing; deterministic." }, { "verb": { "$verb": "accessibility-audit.check-target-size" }, "when": "At the plan's widths; deterministic." }, { "verb": { "$verb": "accessibility-audit.check-media" }, "when": "For every video and audio element." }, { "verb": { "$verb": "accessibility-audit.check-motion" }, "when": "With and without reduced motion; deterministic." }, { "verb": { "$verb": "accessibility-audit.check-text-alternatives" }, "when": "For images, icons, controls and fields." }, { "verb": { "$verb": "accessibility-audit.check-with-assistive-tech" }, "when": "A person tries the key tasks with a screen reader; a person's verb." }, { "verb": { "$verb": "accessibility-audit.write-audit-report" }, "when": "When the checks are in." }, { "verb": { "$verb": "accessibility-audit.review-audit" }, "when": "A different worker or a person rechecks a sample." } ], "guardrails": [ "Every finding cites one success criterion by number, name and level, and says whether it is a failure or advisory.", "AAA criteria and best practices are advisory in an AA audit; they never make a page fail.", "Automated checks find only part of the problems. A report never claims the site is compliant or fully accessible; it says what was checked and what was not.", "Captures and walks are read-only: no form is submitted, nothing is typed into fields and no account is created.", "A person's assistive-technology session is its own verb; an agent never reports one it did not have." ], "worked_examples": [ { "$pin": "audit-checks" }, { "$pin": "example.contrast" }, { "$pin": "example.structure-lint" }, { "$pin": "example.audit-plan" }, { "$pin": "example.keyboard-walk" }, { "$pin": "example.audit-report" } ], "tool": { "$pin": "tool.a11y-checks" }, "complements": [ "The public-good kits pack's check-accessibility covers kit documents; this pack covers web pages and apps.", "The visual and 3D rendering pack's contrast example covers rendered graphics; this pack ships the tested contrast tool.", "Every failure goes to the findings to fixes pack for its fix, verification and guard.", "The screenshot design review pack measures visual design rules from captures and verifies re-renders; this pack audits WCAG 2.2 AA criteria, including keyboard paths, media, motion and a person's assistive-technology session." ] }, "relations": [ [ "requires_noun", "method" ], [ "requires_noun", "wcag-source" ], [ "requires_noun", "wcag-checklist" ], [ "requires_noun", "capture-matrix" ], [ "requires_noun", "keyboard-procedure" ], [ "requires_noun", "contrast-method" ], [ "requires_noun", "layout-rules" ], [ "requires_noun", "media-and-motion-rules" ], [ "requires_noun", "structure-rules" ], [ "requires_noun", "tool.a11y-checks" ], [ "requires_noun", "audit-checks" ] ] }, { "slug": "method", "category": "noun.skill_pack", "role": "method", "title": "How an accessibility audit is planned, checked, reported and reviewed", "category_reason": "A reusable procedure with guardrails, followed by every verb in this pack.", "value": { "lifecycle": [ "plan-audit names the pages, states, widths, themes, input modes and the criteria in scope.", "capture-states saves a screenshot and a rendered DOM snapshot of every planned cell, by digest.", "Each check verb covers its own area and cites a criterion for every result.", "check-with-assistive-tech: a person tries the key tasks with a screen reader.", "write-audit-report turns failures and advisories into findings and states what was not checked.", "review-audit: a different worker or a person reproduces a sample before anyone relies on the report." ], "rules": [ "Every finding cites one success criterion: number, name and level, with failure or advisory.", "In an AA audit, AAA criteria and best practices are advisory.", "Evidence names the address, the state, the width and the theme, and stores screenshots by SHA-256 with the region that shows the problem.", "cannot_tell is an honest result; a check never guesses pass.", "The report's statement follows from its parts: failures_found when any failure exists; no_failures_found_in_what_was_checked only when nothing in scope went unchecked and a person's assistive-technology session ran; otherwise not_claimed.", "Never write that a site is compliant, certified or fully accessible." ], "severity": { "P1": "A failure that stops some people from finishing a key task, such as a keyboard trap or a video with no captions.", "P2": "A failure that makes a task hard or slow, such as low contrast on secondary text or a small target with a workaround.", "P3": "An advisory, or a failure on a minor part with an easy workaround.", "P0": "Not an accessibility level: route exposed personal data or credentials to the security review pack." }, "follow_ups": { "failure": { "$verb": "findings-to-fixes.record-finding" }, "retest": { "$verb": "findings-to-fixes.verify-fix" } }, "sources": [ "W3C Website Accessibility Conformance Evaluation Methodology (WCAG-EM) 1.0, a W3C Working Group Note: scope, explore, sample, audit, report. The steps here are written for this pack." ] } }, { "slug": "wcag-source", "category": "noun.public_source", "role": "standard", "title": "WCAG 2.2, the W3C Recommendation this audit cites", "category_reason": "A public standard, cited by number and linked, never copied.", "value": { "name": "Web Content Accessibility Guidelines (WCAG) 2.2", "publisher": "World Wide Web Consortium (W3C)", "url": "https://www.w3.org/TR/WCAG22/", "understanding": "https://www.w3.org/WAI/WCAG22/Understanding/", "quick_reference": "https://www.w3.org/WAI/WCAG22/quickref/", "publication_status": "W3C Recommendation, first published 5 October 2023. Read the dated version at the address before relying on it.", "notes": [ "WCAG 2.2 added nine success criteria, including 2.4.11 Focus Not Obscured (Minimum), 2.5.7 Dragging Movements, 2.5.8 Target Size (Minimum), 3.2.6 Consistent Help, 3.3.7 Redundant Entry and 3.3.8 Accessible Authentication (Minimum).", "4.1.1 Parsing is obsolete in WCAG 2.2 and is not audited here.", "The criteria's wording belongs to the W3C under its document license; this pack paraphrases and links instead of copying." ] } }, { "slug": "wcag-checklist", "category": "noun.benchmark_suite", "role": "criteria_checklist", "title": "WCAG 2.2 A and AA criteria this audit checks, by area", "category_reason": "A fixed checklist every audited page is checked against.", "value": { "standard": "WCAG 2.2", "target_level": "AA", "criteria": [ { "sc": "2.1.1", "name": "Keyboard", "level": "A", "check": "keyboard", "method": "both", "asks": "Every action works from a keyboard alone, with no timing needed." }, { "sc": "2.1.2", "name": "No Keyboard Trap", "level": "A", "check": "keyboard", "method": "both", "asks": "Focus can always leave a part with the keyboard, or the page says how." }, { "sc": "2.1.4", "name": "Character Key Shortcuts", "level": "A", "check": "keyboard", "method": "manual", "asks": "A single-key shortcut can be turned off or remapped, or works only while its part has focus." }, { "sc": "2.4.1", "name": "Bypass Blocks", "level": "A", "check": "keyboard", "method": "both", "asks": "A skip link, landmarks or headings let people jump past repeated blocks." }, { "sc": "2.4.3", "name": "Focus Order", "level": "A", "check": "keyboard", "method": "manual", "asks": "Focus moves in an order that keeps meaning and operation." }, { "sc": "2.4.7", "name": "Focus Visible", "level": "AA", "check": "keyboard", "method": "both", "asks": "The keyboard focus indicator is visible at every stop." }, { "sc": "2.4.11", "name": "Focus Not Obscured (Minimum)", "level": "AA", "check": "keyboard", "method": "both", "asks": "A focused part is never entirely hidden by sticky headers, banners or other author content." }, { "sc": "3.2.1", "name": "On Focus", "level": "A", "check": "keyboard", "method": "manual", "asks": "Focus alone never changes the context, such as opening a new page." }, { "sc": "1.4.3", "name": "Contrast (Minimum)", "level": "AA", "check": "contrast", "method": "automatic", "asks": "Text has 4.5:1 against its background, or 3:1 when large, in every theme." }, { "sc": "1.4.11", "name": "Non-text Contrast", "level": "AA", "check": "contrast", "method": "both", "asks": "Control boundaries, focus indicators and meaningful graphics have 3:1 against what is next to them." }, { "sc": "1.4.1", "name": "Use of Color", "level": "A", "check": "contrast", "method": "manual", "asks": "Color is never the only way information or an action is shown." }, { "sc": "1.1.1", "name": "Non-text Content", "level": "A", "check": "text_alternatives", "method": "both", "asks": "Images, icons and controls have text alternatives that serve the same purpose; decoration is hidden." }, { "sc": "1.2.1", "name": "Audio-only and Video-only (Prerecorded)", "level": "A", "check": "media", "method": "manual", "asks": "Audio-only has a transcript; video-only has a transcript or an audio track." }, { "sc": "1.2.2", "name": "Captions (Prerecorded)", "level": "A", "check": "media", "method": "both", "asks": "Prerecorded video with sound has synchronized captions." }, { "sc": "1.2.3", "name": "Audio Description or Media Alternative (Prerecorded)", "level": "A", "check": "media", "method": "manual", "asks": "Prerecorded video has audio description or a full text alternative." }, { "sc": "1.2.5", "name": "Audio Description (Prerecorded)", "level": "AA", "check": "media", "method": "manual", "asks": "Prerecorded video has audio description, unless the main audio already conveys every important visual." }, { "sc": "1.4.2", "name": "Audio Control", "level": "A", "check": "media", "method": "both", "asks": "Sound that plays on its own for more than three seconds can be paused or turned down." }, { "sc": "1.4.10", "name": "Reflow", "level": "AA", "check": "reflow", "method": "both", "asks": "At 320 CSS pixels wide, content reads without horizontal scrolling, except content that needs two dimensions." }, { "sc": "1.4.4", "name": "Resize Text", "level": "AA", "check": "reflow", "method": "both", "asks": "Text can grow to 200 percent without losing content or function." }, { "sc": "1.4.12", "name": "Text Spacing", "level": "AA", "check": "reflow", "method": "both", "asks": "Wider line, paragraph, letter and word spacing loses no content." }, { "sc": "1.4.13", "name": "Content on Hover or Focus", "level": "AA", "check": "reflow", "method": "manual", "asks": "Content shown on hover or focus can be dismissed, hovered and stays until dismissed." }, { "sc": "1.3.4", "name": "Orientation", "level": "AA", "check": "reflow", "method": "manual", "asks": "Content works in portrait and landscape unless one is essential." }, { "sc": "2.5.8", "name": "Target Size (Minimum)", "level": "AA", "check": "target_size", "method": "both", "asks": "Pointer targets are at least 24 by 24 CSS pixels, or meet one of the exceptions." }, { "sc": "2.5.7", "name": "Dragging Movements", "level": "AA", "check": "target_size", "method": "manual", "asks": "Anything done by dragging can also be done with single pointer actions." }, { "sc": "2.5.3", "name": "Label in Name", "level": "A", "check": "text_alternatives", "method": "both", "asks": "A control's accessible name contains its visible label text." }, { "sc": "2.2.2", "name": "Pause, Stop, Hide", "level": "A", "check": "motion", "method": "both", "asks": "Motion that starts on its own and lasts more than five seconds can be paused, stopped or hidden." }, { "sc": "2.3.1", "name": "Three Flashes or Below Threshold", "level": "A", "check": "motion", "method": "manual", "asks": "Nothing flashes more than three times in any second, unless below the flash thresholds." }, { "sc": "3.1.1", "name": "Language of Page", "level": "A", "check": "structure", "method": "automatic", "asks": "The page states its main language." }, { "sc": "3.1.2", "name": "Language of Parts", "level": "AA", "check": "structure", "method": "manual", "asks": "Passages in another language are marked with their language." }, { "sc": "1.3.1", "name": "Info and Relationships", "level": "A", "check": "structure", "method": "both", "asks": "Headings, lists, landmarks, tables and labels are in the markup, not only in the look." }, { "sc": "1.3.2", "name": "Meaningful Sequence", "level": "A", "check": "structure", "method": "manual", "asks": "The reading order in the markup keeps the meaning." }, { "sc": "2.4.2", "name": "Page Titled", "level": "A", "check": "structure", "method": "automatic", "asks": "Each page has a title that says what it is." }, { "sc": "2.4.6", "name": "Headings and Labels", "level": "AA", "check": "structure", "method": "manual", "asks": "Headings and labels describe their topic or purpose." }, { "sc": "4.1.2", "name": "Name, Role, Value", "level": "A", "check": "text_alternatives", "method": "both", "asks": "Every control exposes its name, role and state." }, { "sc": "3.3.2", "name": "Labels or Instructions", "level": "A", "check": "text_alternatives", "method": "both", "asks": "Fields that need input have labels or instructions." }, { "sc": "3.3.1", "name": "Error Identification", "level": "A", "check": "manual", "method": "manual", "asks": "An input error is identified and described in text." }, { "sc": "4.1.3", "name": "Status Messages", "level": "AA", "check": "manual", "method": "manual", "asks": "Status messages are announced without moving focus." }, { "sc": "3.3.7", "name": "Redundant Entry", "level": "A", "check": "manual", "method": "manual", "asks": "Information already entered in a process is filled in or selectable, not asked again." }, { "sc": "3.3.8", "name": "Accessible Authentication (Minimum)", "level": "AA", "check": "manual", "method": "manual", "asks": "Signing in needs no memory or puzzle test unless an alternative or help is given." }, { "sc": "2.4.4", "name": "Link Purpose (In Context)", "level": "A", "check": "text_alternatives", "method": "manual", "asks": "Each link's purpose is clear from its text or its context." }, { "sc": "2.4.5", "name": "Multiple Ways", "level": "AA", "check": "manual", "method": "manual", "asks": "A page can be found in more than one way, such as navigation and a site map." }, { "sc": "3.2.3", "name": "Consistent Navigation", "level": "AA", "check": "manual", "method": "manual", "asks": "Repeated navigation appears in the same order on every page." }, { "sc": "3.2.6", "name": "Consistent Help", "level": "A", "check": "manual", "method": "manual", "asks": "Help or contact options appear in the same place on every page." } ], "method_meaning": { "automatic": "A deterministic tool answers it.", "manual": "A person or a careful agent judges it.", "both": "A tool finds candidates and a person or agent confirms them." }, "not_listed": "Other A and AA criteria still apply; a plan that leaves one out lists it under not_in_scope with the reason." } }, { "slug": "capture-matrix", "category": "noun.reflex_procedure", "role": "capture_matrix", "title": "Which states, widths, themes and settings to capture", "category_reason": "A zero-call rule for which cells to capture and check.", "value": { "cells": { "widths_px": [ 1440, 390, 320 ], "themes": "Every theme the surface offers, such as light and dark", "reduced_motion": [ "no-preference", "reduce" ], "input_modes": [ "pointer", "keyboard" ] }, "checks_per_cell": { "contrast": "1440 and 390 in every theme", "structure": "Once per page and state, on the rendered DOM", "keyboard": "1440 and 390, light theme", "reflow": "320, light theme, plus wider text spacing", "target_size": "390 and 1440", "motion": "1440 with both reduced motion settings", "media": "1440" }, "states": [ "The page as loaded", "Menus, dialogs and disclosures opened by keyboard", "Error and empty states reachable without submitting real data" ], "screenshots": "Full page and viewport, PNG, stored by SHA-256; a region is [x, y, width, height] in CSS pixels of the full-page image.", "never": [ "Submit a form, type into a field or create an account to reach a state.", "Use real personal data in any state." ] } }, { "slug": "keyboard-procedure", "category": "noun.reflex_procedure", "role": "keyboard_procedure", "title": "Walking a page with the keyboard alone", "category_reason": "A zero-call procedure: the same page and keys give the same walk.", "value": { "per_stop": [ "The focused element's name, role and box", "Whether the focus indicator is visible: crops of the stop focused and unfocused differ", "Whether it is obscured: the element at the center of its box is the focused one", "Whether the order follows the reading order" ], "page_checks": [ "The first stop is a skip link that moves focus to the main content.", "Escape closes menus and dialogs and returns focus to what opened them.", "No trap: focus leaves every part within a few presses. Native media controls may hold several stops; count until focus leaves.", "Single-character shortcuts (2.1.4) can be turned off or remapped, or act only while their part has focus.", "Every action reachable by pointer is reachable by keyboard (2.1.1)." ], "never": [ "Press Enter on a submit button or type into a field during an audit walk.", "Refocus an element in a way that resets its inner controls and then report a trap." ], "keys": [ "Tab", "Shift+Tab", "Enter on links and disclosures", "Space on buttons that change display only", "Escape", "Arrow keys inside widgets" ] } }, { "slug": "contrast-method", "category": "noun.reflex_procedure", "role": "contrast_method", "title": "Measuring contrast from computed styles", "category_reason": "A zero-call measurement rule implemented by the pack's tested code.", "value": { "pairs": "Take each text element's computed color and the nearest opaque background behind it, compositing transparent layers over the page canvas.", "thresholds": { "1.4.3 normal text": 4.5, "1.4.3 large text": 3.0, "1.4.11 non-text": 3.0, "1.4.6 AAA normal text (advisory)": 7.0, "1.4.6 AAA large text (advisory)": 4.5 }, "large_text": "At least 24 CSS pixels, or at least 18.67 CSS pixels (14 point) and bold (weight 700 or more). Unknown size is judged as normal text.", "rounding": "Pass and fail use the unrounded ratio. Reports show two decimals.", "cannot_tell": "Text over an image or gradient is cannot_tell unless measured at its lightest and darkest points.", "exempt": [ "Logos and brand names", "Disabled controls", "Purely decorative text", "Text that is not visible to anyone, such as an off-screen skip link until it is focused; check it focused" ], "tool": { "$pin": "tool.a11y-checks" } } }, { "slug": "layout-rules", "category": "noun.reflex_procedure", "role": "layout_rules", "title": "Reflow, text spacing and target size rules", "category_reason": "Zero-call geometric rules for three criteria.", "value": { "reflow": { "width_px": 320, "height_px_for_horizontal_content": 256, "pass": "No horizontal scrolling of the page at 320 CSS pixels, and no content or function lost.", "two_dimensional_exceptions": [ "Data tables", "Maps and diagrams", "Video", "Slides shown as slides", "Toolbars", "Code samples with long lines inside their own scroll box" ] }, "text_spacing": { "line_height": "1.5 times the font size", "paragraph_spacing": "2 times the font size", "letter_spacing": "0.12 times the font size", "word_spacing": "0.16 times the font size", "pass": "No text is clipped, overlapped or lost with all four applied. Visually hidden helper text with a 1-pixel box is not clipped content." }, "target_size": { "minimum_css_px": 24, "exceptions_in_order": [ "Spacing: a 24-pixel circle centered on the target touches no other target or circle", "Equivalent: another control on the page does the same and meets the size", "Inline: the target sits in a sentence or block of text", "User agent: the browser draws it unchanged", "Essential: the size is required or legally fixed" ], "measure": "The bounding box of the clickable area; a label tied to a checkbox adds its area." } } }, { "slug": "media-and-motion-rules", "category": "noun.reflex_procedure", "role": "media_and_motion_rules", "title": "Checking video, audio and motion", "category_reason": "A zero-call checklist for media and motion criteria.", "value": { "video": [ "Captions (1.2.2): a captions track in the spoken language that loads, covers the whole running time and matches a sample of the narration.", "Audio description (1.2.5): a described version or descriptions track, or a record that the narration already conveys every important visual; say which, and who judged it.", "Media alternative (1.2.3): a full text alternative with the speech and the important visuals also satisfies level A; a transcript of speech alone does not.", "Controls: play, pause, volume and captions work by keyboard and have names.", "Sound that starts on its own for more than three seconds can be paused or muted (1.4.2)." ], "motion": [ "Motion that starts on its own and lasts more than five seconds has a pause, stop or hide control (2.2.2).", "Flashing (2.3.1) is cannot_tell unless measured with a named flash analysis tool.", "Honoring prefers-reduced-motion is advisory under 2.3.3 (AAA) in an AA audit." ] } }, { "slug": "structure-rules", "category": "noun.reflex_procedure", "role": "structure_rules", "title": "The structure linter's rules and the criteria they relate to", "category_reason": "A zero-call rule list; the pack's code tests keep it equal to the linter's own rules.", "value": { "rules": [ { "id": "page-lang", "sc": "3.1.1", "name": "Language of Page", "level": "A", "conformance": "failure" }, { "id": "page-title", "sc": "2.4.2", "name": "Page Titled", "level": "A", "conformance": "failure" }, { "id": "main-missing", "sc": "2.4.1", "name": "Bypass Blocks", "level": "A", "conformance": "advisory" }, { "id": "main-multiple", "sc": "1.3.1", "name": "Info and Relationships", "level": "A", "conformance": "advisory" }, { "id": "main-nested", "sc": "1.3.1", "name": "Info and Relationships", "level": "A", "conformance": "advisory" }, { "id": "banner-multiple", "sc": "1.3.1", "name": "Info and Relationships", "level": "A", "conformance": "advisory" }, { "id": "contentinfo-multiple", "sc": "1.3.1", "name": "Info and Relationships", "level": "A", "conformance": "advisory" }, { "id": "landmark-unlabeled", "sc": "1.3.1", "name": "Info and Relationships", "level": "A", "conformance": "advisory" }, { "id": "landmark-duplicate-label", "sc": "1.3.1", "name": "Info and Relationships", "level": "A", "conformance": "advisory" }, { "id": "content-outside-landmarks", "sc": "1.3.1", "name": "Info and Relationships", "level": "A", "conformance": "advisory" }, { "id": "h1-missing", "sc": "2.4.6", "name": "Headings and Labels", "level": "AA", "conformance": "advisory" }, { "id": "h1-multiple", "sc": "1.3.1", "name": "Info and Relationships", "level": "A", "conformance": "advisory" }, { "id": "heading-skipped-level", "sc": "1.3.1", "name": "Info and Relationships", "level": "A", "conformance": "advisory" }, { "id": "heading-empty", "sc": "2.4.6", "name": "Headings and Labels", "level": "AA", "conformance": "advisory" }, { "id": "skip-link-target-missing", "sc": "2.4.1", "name": "Bypass Blocks", "level": "A", "conformance": "advisory" } ], "limits": [ "The linter reads markup, not style sheets: content hidden only by a style sheet still counts as visible.", "Run it on a rendered DOM snapshot for pages built by scripts.", "Language of parts (3.1.2) is not linted; it needs a reader." ], "tool": { "$pin": "tool.a11y-checks" } } }, { "slug": "tool.a11y-checks", "category": "noun.code_node", "role": "tool_card", "title": "Tool card: the accessibility checks", "category_reason": "Tested code a worker runs inside a claimed verb, pinned by path, file count and digest.", "value": { "name": "accessibility checks", "code": { "$code": "accessibility-audit" }, "runtime": "Python 3.10 or later, standard library only; nothing to install.", "commands": [ "python -m a11y_checks contrast FOREGROUND BACKGROUND [--size-px N] [--weight W] [--non-text]", "python -m a11y_checks contrast --pairs PAIRS.json", "python -m a11y_checks structure PAGE.html [PAGE.html ...] [--strict]" ], "does": [ "Contrast ratios for text (1.4.3, with 1.4.6 reported separately) and non-text parts (1.4.11), with alpha compositing and the large-text rule.", "Landmark, heading-order, page-language and page-title lint, each result citing its criterion and marked failure or advisory." ], "does_not": [ "Render pages, run page scripts or load style sheets.", "Open a network connection, call a model or write any file.", "Decide conformance; it reports measurements for a worker's check verb." ], "code_license": "MIT", "tests": "The code's tests check published reference ratios and every lint rule, and recompute this pack's two worked examples from source.json, in the library's CI job for the pack." }, "relations": [ [ "verifies", "contrast-method" ], [ "verifies", "structure-rules" ] ] }, { "slug": "example.contrast", "category": "noun.benchmark_suite", "role": "worked_example", "title": "SYNTHETIC worked example: theme colors in light and dark", "synthetic": true, "category_reason": "A golden fixture the validator and the pack's contrast tool both recompute.", "value": { "fixture_kind": "contrast_ratio", "purpose": "Synthetic theme colors checked in both themes, recomputed by the library validator and again by this pack's contrast tool in its own CI job.", "formula": "Relative luminance L = 0.2126 R + 0.7152 G + 0.0722 B of linearized sRGB channels (c/12.92 at or below 0.04045, else ((c + 0.055) / 1.055) ^ 2.4); ratio = (L_lighter + 0.05) / (L_darker + 0.05).", "thresholds": { "normal_text": 4.5, "large_text": 3.0 }, "pairs": [ { "id": "body-text-light", "foreground": "#1f2933", "background": "#ffffff" }, { "id": "muted-text-light", "foreground": "#6b7280", "background": "#ffffff" }, { "id": "placeholder-light", "foreground": "#9ca3af", "background": "#ffffff" }, { "id": "muted-text-dark", "foreground": "#6b7280", "background": "#0f172a" }, { "id": "secondary-text-dark", "foreground": "#9ca3af", "background": "#0f172a" }, { "id": "link-dark", "foreground": "#60a5fa", "background": "#0f172a" }, { "id": "button-text-on-brand", "foreground": "#ffffff", "background": "#2f855a" } ], "reading": [ "normal_text is the 1.4.3 threshold for text under 24 pixels, or under 18.67 pixels when bold; large_text is the 3:1 threshold for larger text.", "The same 3:1 value is the 1.4.11 threshold for control boundaries and focus indicators, a different criterion.", "A token that passes in one theme can fail in the other: check every theme a surface offers." ], "expected": { "body-text-light": { "ratio": 14.76, "normal_text": true, "large_text": true }, "muted-text-light": { "ratio": 4.83, "normal_text": true, "large_text": true }, "placeholder-light": { "ratio": 2.54, "normal_text": false, "large_text": false }, "muted-text-dark": { "ratio": 3.69, "normal_text": false, "large_text": true }, "secondary-text-dark": { "ratio": 7.03, "normal_text": true, "large_text": true }, "link-dark": { "ratio": 7.02, "normal_text": true, "large_text": true }, "button-text-on-brand": { "ratio": 4.54, "normal_text": true, "large_text": true } } } }, { "slug": "example.structure-lint", "category": "noun.benchmark_suite", "role": "worked_example", "title": "SYNTHETIC worked example: landmark, heading and language lint", "synthetic": true, "category_reason": "A golden fixture of pages and expected lint results.", "value": { "purpose": "Five small synthetic pages and the rules the structure linter reports for each. The pack's code recomputes every result in its CI job.", "pages": [ { "id": "clean", "html": "
Two places left.
Open on Saturdays.