diff --git a/.circleci/config.yml b/.circleci/config.yml index f4e3c8b2f3d0e5..5cd32905085335 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -94,6 +94,12 @@ jobs: - code-infra/check-static-changes - code-infra/check-generated: command: pnpm proptypes + - run: + # Fails when a conformance report changes without its rollup being + # regenerated, and when a report rates a criterion ⚙️ Automated + # without citing the test or axe rule that proves it. + name: Accessibility scorecard up to date? + command: pnpm a11y:scorecard:check - code-infra/check-generated: command: pnpm docs:api - code-infra/extract-error-codes diff --git a/docs/data/material/getting-started/accessibility/scorecard.json b/docs/data/material/getting-started/accessibility/scorecard.json new file mode 100644 index 00000000000000..4ce0491cbc9121 --- /dev/null +++ b/docs/data/material/getting-started/accessibility/scorecard.json @@ -0,0 +1,2665 @@ +{ + "standard": "WCAG 2.2 Level A and AA", + "componentCount": 12, + "totals": { + "supports": 207, + "partiallySupports": 23, + "doesNotSupport": 0, + "notApplicable": 387, + "inherited": 43, + "flagged": 46 + }, + "criteria": [ + { + "number": "1.1.1", + "name": "Non-text Content", + "level": "A", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "1.3.1", + "name": "Info and Relationships", + "level": "A", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "1.3.2", + "name": "Meaningful Sequence", + "level": "A", + "conformance": "Supports", + "flagged": true, + "affected": [] + }, + { + "number": "1.3.3", + "name": "Sensory Characteristics", + "level": "A", + "conformance": "Supports", + "flagged": true, + "affected": [] + }, + { + "number": "1.3.5", + "name": "Identify Input Purpose", + "level": "AA", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "1.4.1", + "name": "Use of Color", + "level": "A", + "conformance": "Partially Supports", + "flagged": true, + "affected": ["ToggleButton"] + }, + { + "number": "1.4.3", + "name": "Contrast (Minimum)", + "level": "AA", + "conformance": "Partially Supports", + "flagged": true, + "affected": ["Avatar", "Button", "TextField", "ToggleButton"] + }, + { + "number": "1.4.4", + "name": "Resize Text", + "level": "AA", + "conformance": "Supports", + "flagged": true, + "affected": [] + }, + { + "number": "1.4.5", + "name": "Images of Text", + "level": "AA", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "1.4.10", + "name": "Reflow", + "level": "AA", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "1.4.11", + "name": "Non-text Contrast", + "level": "AA", + "conformance": "Partially Supports", + "flagged": true, + "affected": [ + "AccordionSummary", + "Avatar", + "Button", + "Checkbox", + "LinearProgress", + "Radio", + "Switch", + "TextField", + "ToggleButton" + ] + }, + { + "number": "1.4.12", + "name": "Text Spacing", + "level": "AA", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "2.1.1", + "name": "Keyboard", + "level": "A", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "2.1.2", + "name": "No Keyboard Trap", + "level": "A", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "2.2.2", + "name": "Pause, Stop, Hide", + "level": "A", + "conformance": "Partially Supports", + "flagged": true, + "affected": ["LinearProgress"] + }, + { + "number": "2.3.1", + "name": "Three Flashes or Below Threshold", + "level": "A", + "conformance": "Supports", + "flagged": true, + "affected": [] + }, + { + "number": "2.4.3", + "name": "Focus Order", + "level": "A", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "2.4.4", + "name": "Link Purpose (In Context)", + "level": "A", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "2.4.6", + "name": "Headings and Labels", + "level": "AA", + "conformance": "Supports", + "flagged": true, + "affected": [] + }, + { + "number": "2.4.7", + "name": "Focus Visible", + "level": "AA", + "conformance": "Partially Supports", + "flagged": true, + "affected": ["Button", "Checkbox", "Radio", "Switch", "ToggleButton"] + }, + { + "number": "2.4.11", + "name": "Focus Not Obscured (Minimum)", + "level": "AA", + "conformance": "Supports", + "flagged": true, + "affected": [] + }, + { + "number": "2.5.2", + "name": "Pointer Cancellation", + "level": "A", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "2.5.3", + "name": "Label in Name", + "level": "A", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "2.5.8", + "name": "Target Size (Minimum)", + "level": "AA", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "3.2.1", + "name": "On Focus", + "level": "A", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "3.2.2", + "name": "On Input", + "level": "A", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "3.2.4", + "name": "Consistent Identification", + "level": "AA", + "conformance": "Supports", + "flagged": true, + "affected": [] + }, + { + "number": "3.3.1", + "name": "Error Identification", + "level": "A", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "3.3.2", + "name": "Labels or Instructions", + "level": "A", + "conformance": "Supports", + "flagged": true, + "affected": [] + }, + { + "number": "3.3.3", + "name": "Error Suggestion", + "level": "AA", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "4.1.2", + "name": "Name, Role, Value", + "level": "A", + "conformance": "Supports", + "flagged": false, + "affected": [] + }, + { + "number": "4.1.3", + "name": "Status Messages", + "level": "AA", + "conformance": "Partially Supports", + "flagged": true, + "affected": ["Button", "LinearProgress", "TextField"] + } + ], + "components": [ + { + "component": "Accordion", + "counts": { + "supports": "19", + "partiallySupports": "0", + "doesNotSupport": "0", + "notApplicable": "31", + "inherited": "5", + "flagged": "3/19" + }, + "gaps": [ + "Inherits: ⚠️ **1.4.11 Non-text Contrast** (the summary focus indicator) from [AccordionSummary](../AccordionSummary/accessibility.md)." + ], + "criteria": [ + { + "number": "1.3.2", + "name": "Meaningful Sequence", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": true + }, + { + "number": "1.3.3", + "name": "Sensory Characteristics", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.4.1", + "name": "Use of Color", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": true + }, + { + "number": "1.4.5", + "name": "Images of Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "2.4.11", + "name": "Focus Not Obscured (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "3.2.4", + "name": "Consistent Identification", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.3.1", + "name": "Info and Relationships", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.3", + "name": "Contrast (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": true + }, + { + "number": "2.4.6", + "name": "Headings and Labels", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "4.1.2", + "name": "Name, Role, Value", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.4", + "name": "Resize Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.10", + "name": "Reflow", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.12", + "name": "Text Spacing", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.1.1", + "name": "Keyboard", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.1.2", + "name": "No Keyboard Trap", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.4.3", + "name": "Focus Order", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.2", + "name": "Pointer Cancellation", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "3.2.1", + "name": "On Focus", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "3.2.2", + "name": "On Input", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + } + ], + "summary": { + "rated": 19, + "levelA": 11, + "levelAA": 8, + "supports": 19, + "partiallySupports": 0, + "doesNotSupport": 0, + "verified": 16, + "automated": 9 + } + }, + { + "component": "AccordionSummary", + "counts": { + "supports": "23", + "partiallySupports": "1", + "doesNotSupport": "0", + "notApplicable": "31", + "flagged": "4/24" + }, + "gaps": [ + "⚠️ **1.4.11 Non-text Contrast.** The keyboard-focus indicator is only a background tint (`action.focus`), about 1.3:1 against the adjacent `paper` surface, below the 3:1 minimum." + ], + "criteria": [ + { + "number": "1.3.2", + "name": "Meaningful Sequence", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.3.3", + "name": "Sensory Characteristics", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.4.1", + "name": "Use of Color", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": true + }, + { + "number": "1.4.5", + "name": "Images of Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.4.11", + "name": "Non-text Contrast", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Manual", + "flagged": true + }, + { + "number": "2.4.11", + "name": "Focus Not Obscured (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "3.2.4", + "name": "Consistent Identification", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.1.1", + "name": "Non-text Content", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.3.1", + "name": "Info and Relationships", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.3", + "name": "Contrast (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": true + }, + { + "number": "2.4.6", + "name": "Headings and Labels", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "2.4.7", + "name": "Focus Visible", + "level": "AA", + "conformance": "Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": true + }, + { + "number": "4.1.2", + "name": "Name, Role, Value", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.4", + "name": "Resize Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.10", + "name": "Reflow", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.12", + "name": "Text Spacing", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.1.1", + "name": "Keyboard", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.1.2", + "name": "No Keyboard Trap", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.4.3", + "name": "Focus Order", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.2", + "name": "Pointer Cancellation", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.3", + "name": "Label in Name", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.8", + "name": "Target Size (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "3.2.1", + "name": "On Focus", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "3.2.2", + "name": "On Input", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + } + ], + "summary": { + "rated": 24, + "levelA": 13, + "levelAA": 11, + "supports": 23, + "partiallySupports": 1, + "doesNotSupport": 0, + "verified": 20, + "automated": 11 + } + }, + { + "component": "Avatar", + "counts": { + "supports": "9", + "partiallySupports": "2", + "doesNotSupport": "0", + "notApplicable": "44", + "flagged": "5/11" + }, + "gaps": [ + "⚠️ **1.4.3 Contrast (Minimum).** Default letter and fallback avatars render white text on `grey[400]` (about 1.9:1), and the `deepOrange[500]` example is 3.16:1, both below 4.5:1; arbitrary `stringToColor` backgrounds are unguaranteed.", + "⚠️ **1.4.11 Non-text Contrast.** Meaningful icon-child avatars use the default white-on-`grey[400]` (1.9:1) or low-contrast author backgrounds (for example `green[500]` 2.78:1), below the 3:1 minimum for graphical objects." + ], + "criteria": [ + { + "number": "1.3.2", + "name": "Meaningful Sequence", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": true + }, + { + "number": "1.3.3", + "name": "Sensory Characteristics", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": true + }, + { + "number": "1.4.4", + "name": "Resize Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Component", + "group": "Manual", + "flagged": true + }, + { + "number": "1.4.5", + "name": "Images of Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": false + }, + { + "number": "1.1.1", + "name": "Non-text Content", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": false + }, + { + "number": "1.3.1", + "name": "Info and Relationships", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": false + }, + { + "number": "1.4.1", + "name": "Use of Color", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": true + }, + { + "number": "1.4.3", + "name": "Contrast (Minimum)", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": false + }, + { + "number": "1.4.11", + "name": "Non-text Contrast", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": true + }, + { + "number": "1.4.10", + "name": "Reflow", + "level": "AA", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.12", + "name": "Text Spacing", + "level": "AA", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + } + ], + "summary": { + "rated": 11, + "levelA": 5, + "levelAA": 6, + "supports": 9, + "partiallySupports": 2, + "doesNotSupport": 0, + "verified": 6, + "automated": 2 + } + }, + { + "component": "Button", + "counts": { + "supports": "23", + "partiallySupports": "4", + "doesNotSupport": "0", + "notApplicable": "28", + "flagged": "8/27" + }, + "gaps": [ + "⚠️ **1.4.3 Contrast (Minimum).** `info` and `warning` buttons fall short of 4.5:1 in every variant.", + "⚠️ **1.4.11 Non-text Contrast.** Focus-indicator, border, and icon contrast are untested; `disableRipple`/`disableFocusRipple` remove the `text`/`outlined` focus indicator, and `disableElevation` removes the `contained` one.", + "⚠️ **2.4.7 Focus Visible.** `disableRipple`/`disableFocusRipple` remove the `text`/`outlined` focus indicator; `contained` loses its indicator only when `disableElevation` is combined with them.", + "⚠️ **4.1.3 Status Messages.** The `loading` state adds no live region, so the change may go unannounced." + ], + "criteria": [ + { + "number": "1.3.2", + "name": "Meaningful Sequence", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": true + }, + { + "number": "1.3.3", + "name": "Sensory Characteristics", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": true + }, + { + "number": "1.4.5", + "name": "Images of Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "2.4.11", + "name": "Focus Not Obscured (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": true + }, + { + "number": "3.2.4", + "name": "Consistent Identification", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": true + }, + { + "number": "4.1.3", + "name": "Status Messages", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": true + }, + { + "number": "1.1.1", + "name": "Non-text Content", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.3.1", + "name": "Info and Relationships", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.1", + "name": "Use of Color", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": true + }, + { + "number": "1.4.3", + "name": "Contrast (Minimum)", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.11", + "name": "Non-text Contrast", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": true + }, + { + "number": "2.4.4", + "name": "Link Purpose (In Context)", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "2.4.6", + "name": "Headings and Labels", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "2.4.7", + "name": "Focus Visible", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": true + }, + { + "number": "3.3.2", + "name": "Labels or Instructions", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "4.1.2", + "name": "Name, Role, Value", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.4", + "name": "Resize Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.10", + "name": "Reflow", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.12", + "name": "Text Spacing", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.1.1", + "name": "Keyboard", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.1.2", + "name": "No Keyboard Trap", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.4.3", + "name": "Focus Order", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.2", + "name": "Pointer Cancellation", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.3", + "name": "Label in Name", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.8", + "name": "Target Size (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "3.2.1", + "name": "On Focus", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "3.2.2", + "name": "On Input", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + } + ], + "summary": { + "rated": 27, + "levelA": 15, + "levelAA": 12, + "supports": 23, + "partiallySupports": 4, + "doesNotSupport": 0, + "verified": 19, + "automated": 11 + } + }, + { + "component": "Checkbox", + "counts": { + "supports": "23", + "partiallySupports": "2", + "doesNotSupport": "0", + "notApplicable": "30", + "flagged": "4/25" + }, + "gaps": [ + "⚠️ **1.4.11 Non-text Contrast.** The default checkmark icons clear 3:1 (`warning` is the tightest at 3.11:1). The keyboard focus indicator is untested. `disableRipple`/`disableFocusRipple` or custom icons can drop below 3:1 — a faint custom unchecked box can be about 1.1:1 against the page.", + "⚠️ **2.4.7 Focus Visible.** `disableRipple`/`disableFocusRipple` removes the default focus indicator (the focus ripple). None remains unless the author adds `.Mui-focusVisible` styling or enables the `theme.focusVisible` ring, which draws on the icon and does not depend on the ripple." + ], + "criteria": [ + { + "number": "1.3.2", + "name": "Meaningful Sequence", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.3.3", + "name": "Sensory Characteristics", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.4.5", + "name": "Images of Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "2.4.11", + "name": "Focus Not Obscured (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "3.2.4", + "name": "Consistent Identification", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.1.1", + "name": "Non-text Content", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.3.1", + "name": "Info and Relationships", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.1", + "name": "Use of Color", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": true + }, + { + "number": "1.4.3", + "name": "Contrast (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.11", + "name": "Non-text Contrast", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": true + }, + { + "number": "2.4.6", + "name": "Headings and Labels", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "2.4.7", + "name": "Focus Visible", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": true + }, + { + "number": "3.3.2", + "name": "Labels or Instructions", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": true + }, + { + "number": "4.1.2", + "name": "Name, Role, Value", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.4", + "name": "Resize Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.10", + "name": "Reflow", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.12", + "name": "Text Spacing", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.1.1", + "name": "Keyboard", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.1.2", + "name": "No Keyboard Trap", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.4.3", + "name": "Focus Order", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.2", + "name": "Pointer Cancellation", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.3", + "name": "Label in Name", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.8", + "name": "Target Size (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "3.2.1", + "name": "On Focus", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "3.2.2", + "name": "On Input", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + } + ], + "summary": { + "rated": 25, + "levelA": 14, + "levelAA": 11, + "supports": 23, + "partiallySupports": 2, + "doesNotSupport": 0, + "verified": 21, + "automated": 11 + } + }, + { + "component": "LinearProgress", + "counts": { + "supports": "8", + "partiallySupports": "3", + "doesNotSupport": "0", + "notApplicable": "44", + "flagged": "5/11" + }, + "gaps": [ + "⚠️ **1.4.11 Non-text Contrast.** The fill-vs-track boundary falls below `3:1` for most palette colors in light mode, including the default `primary` (about `2.7:1`); in dark mode only `error` (`2.77:1`) falls short.", + "⚠️ **2.2.2 Pause, Stop, Hide.** The `indeterminate`, `query`, and `buffer` variants animate indefinitely with no built-in pause, stop, or hide control.", + "⚠️ **4.1.3 Status Messages.** The `progressbar` role is not a live region, so `value` changes are not announced unless the surrounding application adds one." + ], + "criteria": [ + { + "number": "1.4.1", + "name": "Use of Color", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Manual", + "flagged": true + }, + { + "number": "1.3.3", + "name": "Sensory Characteristics", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.4.11", + "name": "Non-text Contrast", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Manual", + "flagged": true + }, + { + "number": "2.2.2", + "name": "Pause, Stop, Hide", + "level": "A", + "conformance": "Partially Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": true + }, + { + "number": "2.3.1", + "name": "Three Flashes or Below Threshold", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Manual", + "flagged": true + }, + { + "number": "3.2.4", + "name": "Consistent Identification", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "4.1.3", + "name": "Status Messages", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": true + }, + { + "number": "1.3.1", + "name": "Info and Relationships", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": false + }, + { + "number": "2.4.6", + "name": "Headings and Labels", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "4.1.2", + "name": "Name, Role, Value", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.10", + "name": "Reflow", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + } + ], + "summary": { + "rated": 11, + "levelA": 6, + "levelAA": 5, + "supports": 8, + "partiallySupports": 3, + "doesNotSupport": 0, + "verified": 6, + "automated": 1 + } + }, + { + "component": "Radio", + "counts": { + "supports": "23", + "partiallySupports": "2", + "doesNotSupport": "0", + "notApplicable": "30", + "flagged": "3/25" + }, + "gaps": [ + "⚠️ **1.4.11 Non-text Contrast.** The default dot and circle clear 3:1 (`warning` is the tightest at 3.11:1), but the keyboard focus indicator is untested and `disableRipple`/`disableFocusRipple` or custom icons can drop below 3:1 (the `CustomizedRadios` unchecked circle is about 1.1:1 against the page).", + "⚠️ **2.4.7 Focus Visible.** `disableRipple`/`disableFocusRipple` removes the focus ripple, leaving none unless the author adds `.Mui-focusVisible` styling." + ], + "criteria": [ + { + "number": "1.3.2", + "name": "Meaningful Sequence", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.3.3", + "name": "Sensory Characteristics", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.4.5", + "name": "Images of Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "2.4.11", + "name": "Focus Not Obscured (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "3.2.4", + "name": "Consistent Identification", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "3.3.2", + "name": "Labels or Instructions", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.1.1", + "name": "Non-text Content", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.3.1", + "name": "Info and Relationships", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.1", + "name": "Use of Color", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": true + }, + { + "number": "1.4.3", + "name": "Contrast (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.11", + "name": "Non-text Contrast", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": true + }, + { + "number": "2.4.6", + "name": "Headings and Labels", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "2.4.7", + "name": "Focus Visible", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": true + }, + { + "number": "4.1.2", + "name": "Name, Role, Value", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.4", + "name": "Resize Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.10", + "name": "Reflow", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.12", + "name": "Text Spacing", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.1.1", + "name": "Keyboard", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.1.2", + "name": "No Keyboard Trap", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.4.3", + "name": "Focus Order", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.2", + "name": "Pointer Cancellation", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.3", + "name": "Label in Name", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.8", + "name": "Target Size (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "3.2.1", + "name": "On Focus", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "3.2.2", + "name": "On Input", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + } + ], + "summary": { + "rated": 25, + "levelA": 14, + "levelAA": 11, + "supports": 23, + "partiallySupports": 2, + "doesNotSupport": 0, + "verified": 22, + "automated": 11 + } + }, + { + "component": "RadioGroup", + "counts": { + "supports": "7", + "partiallySupports": "0", + "doesNotSupport": "0", + "notApplicable": "30", + "inherited": "18", + "flagged": "3/7" + }, + "gaps": [], + "criteria": [ + { + "number": "1.4.1", + "name": "Use of Color", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": true + }, + { + "number": "1.3.1", + "name": "Info and Relationships", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "2.4.6", + "name": "Headings and Labels", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": true + }, + { + "number": "3.3.2", + "name": "Labels or Instructions", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": true + }, + { + "number": "4.1.2", + "name": "Name, Role, Value", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "2.1.1", + "name": "Keyboard", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.4.3", + "name": "Focus Order", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + } + ], + "summary": { + "rated": 7, + "levelA": 6, + "levelAA": 1, + "supports": 7, + "partiallySupports": 0, + "doesNotSupport": 0, + "verified": 4, + "automated": 2 + } + }, + { + "component": "Switch", + "counts": { + "supports": "23", + "partiallySupports": "2", + "doesNotSupport": "0", + "notApplicable": "30", + "flagged": "3/25" + }, + "gaps": [ + "⚠️ **1.4.11 Non-text Contrast.** In light mode the thumb and track stay under 3:1. The off-state white thumb is about 2.7:1 on the gray track, and the track is about 2.7:1 on the page. Each checked color measures 1.8:1 to 2.6:1 thumb-on-track. The focus indicator is untested, and `disableRipple` removes it.", + "⚠️ **2.4.7 Focus Visible.** `disableRipple`/`disableFocusRipple` removes the default focus indicator (the focus ripple), leaving none unless the author adds `.Mui-focusVisible` styling." + ], + "criteria": [ + { + "number": "1.3.2", + "name": "Meaningful Sequence", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.3.3", + "name": "Sensory Characteristics", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.4.5", + "name": "Images of Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "2.4.11", + "name": "Focus Not Obscured (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "3.2.4", + "name": "Consistent Identification", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "3.3.2", + "name": "Labels or Instructions", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.1.1", + "name": "Non-text Content", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.3.1", + "name": "Info and Relationships", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.1", + "name": "Use of Color", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": true + }, + { + "number": "1.4.3", + "name": "Contrast (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.11", + "name": "Non-text Contrast", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": true + }, + { + "number": "2.4.6", + "name": "Headings and Labels", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "2.4.7", + "name": "Focus Visible", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": true + }, + { + "number": "4.1.2", + "name": "Name, Role, Value", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.4", + "name": "Resize Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.10", + "name": "Reflow", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.12", + "name": "Text Spacing", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.1.1", + "name": "Keyboard", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.1.2", + "name": "No Keyboard Trap", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.4.3", + "name": "Focus Order", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.2", + "name": "Pointer Cancellation", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.3", + "name": "Label in Name", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.8", + "name": "Target Size (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "3.2.1", + "name": "On Focus", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "3.2.2", + "name": "On Input", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + } + ], + "summary": { + "rated": 25, + "levelA": 14, + "levelAA": 11, + "supports": 23, + "partiallySupports": 2, + "doesNotSupport": 0, + "verified": 22, + "automated": 11 + } + }, + { + "component": "TextField", + "counts": { + "supports": "25", + "partiallySupports": "3", + "doesNotSupport": "0", + "notApplicable": "27", + "flagged": "5/28" + }, + "gaps": [ + "⚠️ **1.4.3 Contrast (Minimum).** The placeholder (about 2.55:1), the focused `warning` label (3.11:1), and the focused color labels on the filled surface (`primary` 4.04:1) fall short of 4.5:1.", + "⚠️ **1.4.11 Non-text Contrast.** The default `outlined` resting border (`rgba(0, 0, 0, 0.23)`, about 1.74:1) is below 3:1; the `filled` underline (about 3.03:1) clears it only marginally.", + "⚠️ **4.1.3 Status Messages.** A dynamically shown error is associated to the input but is not a live region, so it may go unannounced when focus is elsewhere." + ], + "criteria": [ + { + "number": "1.3.2", + "name": "Meaningful Sequence", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.3.3", + "name": "Sensory Characteristics", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.4.5", + "name": "Images of Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.4.11", + "name": "Non-text Contrast", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Manual", + "flagged": true + }, + { + "number": "2.4.7", + "name": "Focus Visible", + "level": "AA", + "conformance": "Supports", + "responsibility": "Component", + "group": "Manual", + "flagged": true + }, + { + "number": "2.4.11", + "name": "Focus Not Obscured (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "3.2.4", + "name": "Consistent Identification", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "3.3.3", + "name": "Error Suggestion", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "4.1.3", + "name": "Status Messages", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": true + }, + { + "number": "1.3.1", + "name": "Info and Relationships", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.3.5", + "name": "Identify Input Purpose", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.1", + "name": "Use of Color", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": true + }, + { + "number": "1.4.3", + "name": "Contrast (Minimum)", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": false + }, + { + "number": "2.4.6", + "name": "Headings and Labels", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "3.3.2", + "name": "Labels or Instructions", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": true + }, + { + "number": "4.1.2", + "name": "Name, Role, Value", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.4", + "name": "Resize Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.10", + "name": "Reflow", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.12", + "name": "Text Spacing", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.1.1", + "name": "Keyboard", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.1.2", + "name": "No Keyboard Trap", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.4.3", + "name": "Focus Order", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.2", + "name": "Pointer Cancellation", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.3", + "name": "Label in Name", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.8", + "name": "Target Size (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "3.2.1", + "name": "On Focus", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "3.2.2", + "name": "On Input", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "3.3.1", + "name": "Error Identification", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + } + ], + "summary": { + "rated": 28, + "levelA": 14, + "levelAA": 14, + "supports": 25, + "partiallySupports": 3, + "doesNotSupport": 0, + "verified": 23, + "automated": 12 + } + }, + { + "component": "ToggleButton", + "counts": { + "supports": "20", + "partiallySupports": "4", + "doesNotSupport": "0", + "notApplicable": "31", + "flagged": "2/24" + }, + "gaps": [ + "⚠️ **1.4.1 Use of Color.** For the color variants (`primary`, `error`, `info`, `success`), the selected and unselected labels are near-identical in grayscale lightness, so the pressed state is conveyed almost entirely by hue.", + "⚠️ **1.4.3 Contrast (Minimum).** When selected, the `primary`, `error`, `info`, and `warning` labels (their `color.main` text over the tinted selected background) fall short of 4.5:1.", + "⚠️ **1.4.11 Non-text Contrast.** The focus indicator is the ripple; `disableRipple`/`disableFocusRipple` remove it, leaving no focus indicator. The selected-state fill is untested for 3:1.", + "⚠️ **2.4.7 Focus Visible.** `disableRipple`/`disableFocusRipple` remove the only keyboard focus indicator; the Toggle Button has no box-shadow fallback." + ], + "criteria": [ + { + "number": "1.3.2", + "name": "Meaningful Sequence", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.3.3", + "name": "Sensory Characteristics", + "level": "A", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.4.5", + "name": "Images of Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "2.4.11", + "name": "Focus Not Obscured (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "3.2.4", + "name": "Consistent Identification", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "flagged": false + }, + { + "number": "1.1.1", + "name": "Non-text Content", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.3.1", + "name": "Info and Relationships", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.1", + "name": "Use of Color", + "level": "A", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": true + }, + { + "number": "1.4.3", + "name": "Contrast (Minimum)", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.11", + "name": "Non-text Contrast", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": true + }, + { + "number": "2.4.6", + "name": "Headings and Labels", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "2.4.7", + "name": "Focus Visible", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": false + }, + { + "number": "4.1.2", + "name": "Name, Role, Value", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.4", + "name": "Resize Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.10", + "name": "Reflow", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.12", + "name": "Text Spacing", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.1.1", + "name": "Keyboard", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.1.2", + "name": "No Keyboard Trap", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.4.3", + "name": "Focus Order", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.2", + "name": "Pointer Cancellation", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.3", + "name": "Label in Name", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.5.8", + "name": "Target Size (Minimum)", + "level": "AA", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "3.2.1", + "name": "On Focus", + "level": "A", + "conformance": "Supports", + "responsibility": "Component", + "group": "Automated", + "flagged": false + }, + { + "number": "3.2.2", + "name": "On Input", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + } + ], + "summary": { + "rated": 24, + "levelA": 13, + "levelAA": 11, + "supports": 20, + "partiallySupports": 4, + "doesNotSupport": 0, + "verified": 22, + "automated": 11 + } + }, + { + "component": "ToggleButtonGroup", + "counts": { + "supports": "4", + "partiallySupports": "0", + "doesNotSupport": "0", + "notApplicable": "31", + "inherited": "20", + "flagged": "1/4" + }, + "gaps": [], + "criteria": [ + { + "number": "1.3.1", + "name": "Info and Relationships", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": false + }, + { + "number": "2.4.6", + "name": "Headings and Labels", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": true + }, + { + "number": "4.1.2", + "name": "Name, Role, Value", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Manual", + "flagged": false + }, + { + "number": "1.4.10", + "name": "Reflow", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + } + ], + "summary": { + "rated": 4, + "levelA": 2, + "levelAA": 2, + "supports": 4, + "partiallySupports": 0, + "doesNotSupport": 0, + "verified": 3, + "automated": 1 + } + } + ] +} diff --git a/package.json b/package.json index f1f5df9b020078..a4507b561eec53 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "release:build": "lerna run --concurrency 8 --no-private build --skip-nx-cache", "release:changelog": "node scripts/releaseChangelog.mjs", "release:pack": "tsx scripts/releasePack.mts", + "a11y:scorecard": "node scripts/a11yScorecard.mjs", + "a11y:scorecard:check": "node scripts/a11yScorecard.mjs --check", "docs:api": "rimraf --glob ./docs/pages/**/api-docs ./docs/pages/**/api && pnpm docs:api:build", "docs:api:build": "tsx ./scripts/buildApiDocs/index.ts", "docs:llms:build": "rimraf --glob ./docs/public/material-ui/ && tsx ./scripts/buildLlmsDocs/index.ts --projectSettings ./packages-internal/api-docs-builder-core/materialUi/projectSettings.ts", diff --git a/packages/mui-material/src/Button/accessibility.md b/packages/mui-material/src/Button/accessibility.md index cd11cc4acfbf2e..b4ed1c46fe53c0 100644 --- a/packages/mui-material/src/Button/accessibility.md +++ b/packages/mui-material/src/Button/accessibility.md @@ -319,7 +319,7 @@ Every other `variant` × `color` combination clears `4.5:1`; the closest pass is `✅ Supports` · `◐ Shared` -- The visible text is the accessible name: the children become the name, decorative MUI icons are hidden (a custom icon node is not, unless the author hides it), and `loadingPosition="center"` keeps the label in the name despite `color: transparent`. +- The visible text is the accessible name: the children become the name, decorative MUI icons are hidden (a custom icon node is not, unless the author hides it), and `loadingPosition="center"` keeps the label in the name despite `color: transparent`. Confirmed by a unit test in [`./Button.test.js`](./Button.test.js) (the visible text is the accessible name, with and without a decorative icon). - An `aria-label` that omits or reorders the visible words breaks this. Compare the visible text to the computed name. #### 2.5.8 Target Size (Minimum) · AA diff --git a/packages/mui-material/src/accessibility.md b/packages/mui-material/src/accessibility.md index 80234337ae6c20..e61246f6e0c2b5 100644 --- a/packages/mui-material/src/accessibility.md +++ b/packages/mui-material/src/accessibility.md @@ -61,19 +61,69 @@ Criteria are grouped by testing method, and roughly sorted by descending order o Components are rated in isolation against WCAG 2.2 A and AA. The levels are [cumulative](https://www.w3.org/WAI/WCAG2AA-Conformance), that is, AA includes all of A. +## How it works + +A report is a **claim**; a test is the **proof**. The two are kept apart on purpose: the reports are what the public page is built from, and CI's job is to stop a claim standing without proof behind it. + +### The claim + +Each `/accessibility.md` rates every applicable success criterion, and records how it is tested and who is responsible. That file is written by hand — it cannot be generated, because most criteria need human judgment. + +### The proof + +Three kinds of test, none of which writes anything into a report: + +| Kind | Where | Covers | +| :---------------------- | :------------------------------------------------------- | :----------------------------------------------------------------------------------------- | +| Unit tests | `/.test.js`, named `it('2.1.1 …')` | Behavior: keyboard operation, focus order, pointer cancellation, accessible naming | +| axe-core | The docs demos, inside the Playwright loop | The mechanical layer: ARIA, labels, text contrast, target size | +| Playwright layout suite | `test/regressions/index.test.js` | What axe has no rule for: reflow at 320px, 200% text size, the WCAG text-spacing overrides | + +axe results are written to `docs/data/material/components/{slug}/{slug}.a11y.json` and committed, so a change that alters them fails CI. **Those files are a tripwire, not a data source** — nothing downstream reads them. + +### The rollup + +`pnpm a11y:scorecard` reads every `/accessibility.md` and regenerates three things: + +- the [Reports](#reports) table below +- the summary table on the public [accessibility conformance page](../../../docs/data/material/getting-started/accessibility/accessibility.md), between its `scorecard` markers +- `docs/data/material/getting-started/accessibility/scorecard.json`, the machine-readable rollup + +None of those numbers are typed by hand. Edit the reports and re-run the command. + +### What CI enforces + +| Job | Check | +| :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `test_unit` | The unit tests pass. | +| `test_regressions` | axe and the layout suite pass, and the committed `*.a11y.json` still match. | +| `test_static` | `pnpm a11y:scorecard:check` — the generated tables are current, and no criterion is rated ⚙️ Automated while still flagged `🚩` or without citing the test or axe rule that proves it. | + +That last check is the link between claim and proof. It is a **citation** check, not a mechanical trace: it verifies the entry names its evidence, not that the named test genuinely proves the criterion. That judgment stays with the reviewer. + ## Reports -| Component | ✅ Supports | ⚠️ Partially Supports | ❌ Does Not Support | ➖ Not Applicable | -| :-------------------------------------------------------- | :---------- | :-------------------- | :------------------ | :---------------- | -| [Accordion](./Accordion/accessibility.md) | 19 | 0 | 0 | 31 | -| [AccordionSummary](./AccordionSummary/accessibility.md) | 23 | 1 | 0 | 31 | -| [Avatar](./Avatar/accessibility.md) | 9 | 2 | 0 | 44 | -| [Button](./Button/accessibility.md) | 23 | 4 | 0 | 28 | -| [Checkbox](./Checkbox/accessibility.md) | 23 | 2 | 0 | 30 | -| [LinearProgress](./LinearProgress/accessibility.md) | 8 | 3 | 0 | 44 | -| [Radio](./Radio/accessibility.md) | 23 | 2 | 0 | 30 | -| [RadioGroup](./RadioGroup/accessibility.md) | 7 | 0 | 0 | 30 | -| [Switch](./Switch/accessibility.md) | 23 | 2 | 0 | 30 | -| [TextField](./TextField/accessibility.md) | 25 | 3 | 0 | 27 | -| [ToggleButton](./ToggleButton/accessibility.md) | 20 | 4 | 0 | 31 | -| [ToggleButtonGroup](./ToggleButtonGroup/accessibility.md) | 4 | 0 | 0 | 31 | +Generated by `pnpm a11y:scorecard` from the count table at the top of each +component report. Edit the reports, not this table. + + + +| Component | ✅ Supports | ⚠️ Partially Supports | ❌ Does Not Support | ➖ Not Applicable | 🚩 Flagged | +| :-------------------------------------------------------- | :---------- | :-------------------- | :------------------ | :---------------- | :--------- | +| [Accordion](./Accordion/accessibility.md) | 19 | 0 | 0 | 31 | 3/19 | +| [AccordionSummary](./AccordionSummary/accessibility.md) | 23 | 1 | 0 | 31 | 4/24 | +| [Avatar](./Avatar/accessibility.md) | 9 | 2 | 0 | 44 | 5/11 | +| [Button](./Button/accessibility.md) | 23 | 4 | 0 | 28 | 8/27 | +| [Checkbox](./Checkbox/accessibility.md) | 23 | 2 | 0 | 30 | 4/25 | +| [LinearProgress](./LinearProgress/accessibility.md) | 8 | 3 | 0 | 44 | 5/11 | +| [Radio](./Radio/accessibility.md) | 23 | 2 | 0 | 30 | 3/25 | +| [RadioGroup](./RadioGroup/accessibility.md) | 7 | 0 | 0 | 30 | 3/7 | +| [Switch](./Switch/accessibility.md) | 23 | 2 | 0 | 30 | 3/25 | +| [TextField](./TextField/accessibility.md) | 25 | 3 | 0 | 27 | 5/28 | +| [ToggleButton](./ToggleButton/accessibility.md) | 20 | 4 | 0 | 31 | 2/24 | +| [ToggleButtonGroup](./ToggleButtonGroup/accessibility.md) | 4 | 0 | 0 | 31 | 1/4 | +| **12 components** | **207** | **23** | **0** | **387** | **46** | + + + +The same rollup feeds the public [accessibility conformance page](../../../docs/data/material/getting-started/accessibility/accessibility.md), which restates these results for procurement in VPAT form. diff --git a/packages/mui-material/src/manual-testing.md b/packages/mui-material/src/manual-testing.md new file mode 100644 index 00000000000000..ab6488449a9550 --- /dev/null +++ b/packages/mui-material/src/manual-testing.md @@ -0,0 +1,178 @@ +# Manual accessibility checks + +Generated by `pnpm a11y:scorecard`. Edit the component reports, not this file. + +The accessibility checks a person still has to make by eye when changing a component. +Anything not listed here is covered by a test — see [How it works](./accessibility.md#how-it-works). + +Two things are deliberately left out: criteria a deterministic test already proves +(⚙️ Automated in the reports), and criteria that depend on the application rather than +the component (○ Author). + +| Marker | Meaning | +| :----- | :---------------------------------------------------------------- | +| 🔍 | No automation. Check it by hand. | +| 🔁 | Automation catches regressions; confirm the judgment still holds. | + +## What to re-check, by kind of change + +Run the rows matching what you touched, for the component you touched. + +| If your change touches… | Re-check | +| :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------ | +| Styling, theme, or palette | 1.4.1 Use of Color; 1.4.3 Contrast (Minimum); 1.4.4 Resize Text; 1.4.5 Images of Text; 1.4.11 Non-text Contrast | +| DOM structure, roles, or ARIA | 1.1.1 Non-text Content; 1.3.1 Info and Relationships; 1.3.2 Meaningful Sequence; 4.1.2 Name, Role, Value; 4.1.3 Status Messages | +| Keyboard, focus, or pointer handling | 2.4.4 Link Purpose (In Context); 2.4.6 Headings and Labels; 2.4.7 Focus Visible | +| Labels, errors, or validation | 3.3.2 Labels or Instructions | +| Motion or timing | 2.2.2 Pause, Stop, Hide | +| Other | 2.3.1 Three Flashes or Below Threshold | + +## Accordion + +6 checks · full procedures in [`Accordion/accessibility.md`](./Accordion/accessibility.md) + +- [ ] 🔍 **1.3.2 Meaningful Sequence** — the header is announced before its panel content. +- [ ] 🔍 **1.4.1 Use of Color** — the state stays distinguishable without color. +- [ ] 🔁 **1.3.1 Info and Relationships** — the header is exposed as a heading and the panel as a region named by it. +- [ ] 🔁 **1.4.3 Contrast (Minimum)** — at least 4.5:1 for the summary label and panel text. Disabled summaries are exempt. +- [ ] 🔁 **2.4.6 Headings and Labels** — every heading describes its section. +- [ ] 🔁 **4.1.2 Name, Role, Value** — state, role, and the header-to-region name are all exposed. + +## AccordionSummary + +8 checks · full procedures in [`AccordionSummary/accessibility.md`](./AccordionSummary/accessibility.md) + +- [ ] 🔍 **1.4.1 Use of Color** — the open and closed states stay distinguishable without color. +- [ ] 🔍 **1.4.11 Non-text Contrast** — the focus indicator and the icon are each at least 3:1. The focus indicator is the known shortfall. +- [ ] 🔁 **1.1.1 Non-text Content** — every summary's announced name matches its section, and the chevron is silent. +- [ ] 🔁 **1.3.1 Info and Relationships** — role and state match the visual presentation. +- [ ] 🔁 **1.4.3 Contrast (Minimum)** — at least 4.5:1 for the label text. Disabled summaries are exempt. +- [ ] 🔁 **2.4.6 Headings and Labels** — every label describes its section, not a vague "Details". +- [ ] 🔁 **2.4.7 Focus Visible** — every keyboard-focused summary shows a visible indicator. +- [ ] 🔁 **4.1.2 Name, Role, Value** — name, role, and state are correct for the native and non-native summaries. + +## Avatar + +7 checks · full procedures in [`Avatar/accessibility.md`](./Avatar/accessibility.md) + +- [ ] 🔍 **1.4.4 Resize Text** — initials and fallback text stay fully visible at 200% page zoom. The container scales with the page. +- [ ] 🔍 **1.4.5 Images of Text** — avatar initials and fallback are real text; the component renders no image of text. +- [ ] 🔍 **1.1.1 Non-text Content** — every informative avatar exposes a text alternative (`img` `alt` or an author-provided label); decorative and no-image avatars are silent. The component does its part; an informative avatar with no `alt` is an authoring failure. +- [ ] 🔍 **1.3.1 Info and Relationships** — the image-to-name relationship is programmatically available via native `img` and `alt`. +- [ ] 🔍 **1.4.1 Use of Color** — no information conveyed by the avatar is lost in grayscale; any color-coded meaning an author adds is paired with a non-color cue. +- [ ] 🔍 **1.4.3 Contrast (Minimum)** — all letter and fallback text meets `4.5:1` (or `3:1` if it's sized to qualify as large text). The default gray letter avatar, the `deepOrange[500]` example, and arbitrary `stringToColor` backgrounds do not meet this without author color overrides. +- [ ] 🔍 **1.4.11 Non-text Contrast** — any icon that conveys meaning meets `3:1` against its background; the decorative container shape and `Person` placeholder are exempt, and the boundary stays visible in forced-colors mode. + +## Button + +11 checks · full procedures in [`Button/accessibility.md`](./Button/accessibility.md) + +- [ ] 🔍 **4.1.3 Status Messages** — the change is announced without the user moving focus to it. It is not, because the component adds no live region; wrap the status in `role="status"` to fix it. +- [ ] 🔁 **1.1.1 Non-text Content** — every button's announced name matches its purpose, and decorative icons are silent. +- [ ] 🔁 **1.3.1 Info and Relationships** — role and state match the visual presentation for every variant. +- [ ] 🔁 **1.4.1 Use of Color** — meaning/purpose of the Button can be understood without color. +- [ ] 🔁 **1.4.3 Contrast (Minimum)** — at least `4.5:1`, or `3:1` for large text, in the resting, hover, and active states. Disabled is exempt; `info` and `warning` are the known failures. +- [ ] 🔁 **1.4.11 Non-text Contrast** — border, focus indicator, and any meaningful icon are each at least `3:1`. Disabled and purely decorative parts are exempt. +- [ ] 🔁 **2.4.4 Link Purpose (In Context)** — a user can tell where the link goes. Replace vague labels like "Learn more" or "click here". +- [ ] 🔁 **2.4.6 Headings and Labels** — every label describes its action ("Submit order", not "OK"). +- [ ] 🔁 **2.4.7 Focus Visible** — every keyboard-focused button shows a visible indicator, including under `disableRipple`, `disableFocusRipple`, and `disableElevation`. +- [ ] 🔁 **3.3.2 Labels or Instructions** — the label clearly tells the user what to upload, not just "Upload". +- [ ] 🔁 **4.1.2 Name, Role, Value** — name, role, and state are correct for every variant, and state changes are announced. + +## Checkbox + +9 checks · full procedures in [`Checkbox/accessibility.md`](./Checkbox/accessibility.md) + +- [ ] 🔁 **1.1.1 Non-text Content** — every checkbox exposes a non-empty accessible name and the checkmark icon stays `aria-hidden`. +- [ ] 🔁 **1.3.1 Info and Relationships** — role, states, label association, and group name are programmatically determinable, and any helper/error text is tied to the control via author-supplied `aria-describedby`. +- [ ] 🔁 **1.4.1 Use of Color** — the three states are distinguishable by shape, and any error state is conveyed by more than color. +- [ ] 🔁 **1.4.3 Contrast (Minimum)** — label and helper/error text meet `4.5:1` (`3:1` for large text) against the actual background. +- [ ] 🔁 **1.4.11 Non-text Contrast** — the indicator in every active state (unchecked outline, checked fill, indeterminate dash) and the focus indicator are `3:1` against adjacent colors; disabled is exempt. +- [ ] 🔁 **2.4.6 Headings and Labels** — every provided label and legend describes its purpose. +- [ ] 🔁 **2.4.7 Focus Visible** — every keyboard-focused checkbox shows a visible indicator, including `disableRipple` via author-supplied styles. Today a bare `disableRipple` checkbox shows none. +- [ ] 🔁 **3.3.2 Labels or Instructions** — every checkbox has a label or instruction presented to all users, via `FormControlLabel`, adjacent text, or clear context. +- [ ] 🔁 **4.1.2 Name, Role, Value** — the role, the checked/unchecked/mixed/disabled state with change notification, and an author-supplied accessible name are all programmatically determinable. + +## LinearProgress + +8 checks · full procedures in [`LinearProgress/accessibility.md`](./LinearProgress/accessibility.md) + +- [ ] 🔍 **1.4.1 Use of Color** +- [ ] 🔍 **1.4.11 Non-text Contrast** — the fill-vs-track contrast is at least `3:1`. In light mode `primary`, `error`, `warning`, `info`, and `success` fail (only `secondary` passes); in dark mode only `error` fails. +- [ ] 🔍 **2.2.2 Pause, Stop, Hide** — the user can pause, stop, or hide the motion, or it stops within five seconds. The component supplies no such control; a full-viewport (essential) loader, or a bar removed once loading finishes, is what keeps it conformant. +- [ ] 🔍 **2.3.1 Three Flashes or Below Threshold** — no part flashes more than three times per second. +- [ ] 🔍 **4.1.3 Status Messages** — the change is announced without the user moving focus to it. It is not by default; add a polite live region in the application to convey it. +- [ ] 🔁 **1.3.1 Info and Relationships** — role and value match the variant. +- [ ] 🔁 **2.4.6 Headings and Labels** — every name describes its operation. +- [ ] 🔁 **4.1.2 Name, Role, Value** — name, role, and value are correct for every variant. + +## Radio + +8 checks · full procedures in [`Radio/accessibility.md`](./Radio/accessibility.md) + +- [ ] 🔁 **1.1.1 Non-text Content** — every radio exposes a non-empty accessible name and the dot and circle stay `aria-hidden`. +- [ ] 🔁 **1.3.1 Info and Relationships** — role, state, and label association are programmatically determinable, and any helper or error text is tied to the control via author-supplied `aria-describedby`. +- [ ] 🔁 **1.4.1 Use of Color** — checked and unchecked are distinguishable by the dot, and any error state is conveyed by more than color. +- [ ] 🔁 **1.4.3 Contrast (Minimum)** — label and helper or error text meet `4.5:1` (`3:1` for large text) against the actual background. +- [ ] 🔁 **1.4.11 Non-text Contrast** — the dot and circle in every active state and the focus indicator are `3:1` against adjacent colors; disabled is exempt. +- [ ] 🔁 **2.4.6 Headings and Labels** — every provided label describes its purpose. +- [ ] 🔁 **2.4.7 Focus Visible** — every keyboard-focused radio shows a visible indicator, including `disableRipple` via author-supplied styles. +- [ ] 🔁 **4.1.2 Name, Role, Value** — the role, the checked or unchecked and disabled state with change notification, and an author-supplied accessible name are all programmatically determinable. + +## RadioGroup + +5 checks · full procedures in [`RadioGroup/accessibility.md`](./RadioGroup/accessibility.md) + +- [ ] 🔍 **1.4.1 Use of Color** — the selected option is distinguishable by the dot, and any error state is conveyed by more than color. +- [ ] 🔁 **1.3.1 Info and Relationships** — the radios are exposed as one `radiogroup`, and the name and any description are set by the author. +- [ ] 🔁 **2.4.6 Headings and Labels** — every group label describes the question it asks. +- [ ] 🔁 **3.3.2 Labels or Instructions** — every group has a visible label or instruction. A group with no visible label fails. +- [ ] 🔁 **4.1.2 Name, Role, Value** — the group exposes its `radiogroup` role, an author-supplied name, and the current selected value with change notification. + +## Switch + +8 checks · full procedures in [`Switch/accessibility.md`](./Switch/accessibility.md) + +- [ ] 🔁 **1.1.1 Non-text Content** — every switch exposes a non-empty accessible name. +- [ ] 🔁 **1.3.1 Info and Relationships** — role, state, label association, and group name are programmatically determinable, and any helper text is tied to the control via author-supplied `aria-describedby`. +- [ ] 🔁 **1.4.1 Use of Color** — on and off are distinguishable by thumb position, and any error state is conveyed by more than color. +- [ ] 🔁 **1.4.3 Contrast (Minimum)** — label and helper text meet `4.5:1` (`3:1` for large text) against the actual background. +- [ ] 🔁 **1.4.11 Non-text Contrast** — the thumb against the track, the track against the page, and the focus indicator are each at least `3:1`; disabled is exempt. Today the default thumb and track are the known failures. +- [ ] 🔁 **2.4.6 Headings and Labels** — every provided label and legend describes its purpose. +- [ ] 🔁 **2.4.7 Focus Visible** — every keyboard-focused switch shows a visible indicator, including `disableRipple` via author-supplied styles. Today a bare `disableRipple` switch shows none. +- [ ] 🔁 **4.1.2 Name, Role, Value** — the role, the on/off state with change notification, and an author-supplied accessible name are all programmatically determinable. + +## TextField + +9 checks · full procedures in [`TextField/accessibility.md`](./TextField/accessibility.md) + +- [ ] 🔍 **1.4.11 Non-text Contrast** — the resting boundary and the focus indicator are each `3:1` against adjacent colors. Today the resting `outlined` border is the known failure. +- [ ] 🔍 **2.4.7 Focus Visible** — every keyboard-focused field shows a visible indicator. +- [ ] 🔍 **4.1.3 Status Messages** — the error is announced without the user moving focus to it. +- [ ] 🔁 **1.3.1 Info and Relationships** — label, description, required, and invalid relationships are programmatically determinable. +- [ ] 🔁 **1.4.1 Use of Color** — the error state is conveyed by more than color (the `helperText` message), not the red alone. +- [ ] 🔁 **1.4.3 Contrast (Minimum)** — label, value, placeholder, and error text meet `4.5:1` (`3:1` for large text) against the actual background. The placeholder, focused `warning`, and filled-surface error text are the known failures. +- [ ] 🔁 **2.4.6 Headings and Labels** — every provided label describes its field. +- [ ] 🔁 **3.3.2 Labels or Instructions** — each field has a visible label or instruction. A placeholder-only or `aria-label`-only field fails. +- [ ] 🔁 **4.1.2 Name, Role, Value** — name, role, value, and the required and invalid states are programmatically determinable. + +## ToggleButton + +8 checks · full procedures in [`ToggleButton/accessibility.md`](./ToggleButton/accessibility.md) + +- [ ] 🔁 **1.1.1 Non-text Content** — every toggle's announced name matches its purpose, and decorative icons are silent. +- [ ] 🔁 **1.3.1 Info and Relationships** — role and state match the visual presentation for every variant. +- [ ] 🔁 **1.4.1 Use of Color** — the control's meaning survives without color. The `standard` selected state stays distinguishable; the color variants do not, since their pressed cue is hue-dominant. +- [ ] 🔁 **1.4.3 Contrast (Minimum)** — at least 4.5:1, or 3:1 for large text, in the resting and hover states. Disabled is exempt; selected `primary`, `error`, `info`, and `warning` are the known failures. +- [ ] 🔁 **1.4.11 Non-text Contrast** — the focus indicator and any meaningful icon are each at least 3:1, and the pressed state stays identifiable. Disabled parts are exempt. +- [ ] 🔁 **2.4.6 Headings and Labels** — every label describes its action ("Bold", not "B"). +- [ ] 🔁 **2.4.7 Focus Visible** — every keyboard-focused toggle shows a visible indicator, including under `disableRipple` and `disableFocusRipple`. +- [ ] 🔁 **4.1.2 Name, Role, Value** — name, role, and state are correct for every variant, and state changes are announced. + +## ToggleButtonGroup + +3 checks · full procedures in [`ToggleButtonGroup/accessibility.md`](./ToggleButtonGroup/accessibility.md) + +- [ ] 🔍 **1.3.1 Info and Relationships** — the toggles are exposed as one group, named when the author supplies a label. +- [ ] 🔍 **2.4.6 Headings and Labels** — every group label describes the set of toggles it contains. +- [ ] 🔍 **4.1.2 Name, Role, Value** — the group exposes its role and author-supplied name, and each toggle's pressed state and its changes are announced. diff --git a/scripts/a11yScorecard.mjs b/scripts/a11yScorecard.mjs new file mode 100644 index 00000000000000..87ccc0577f78ba --- /dev/null +++ b/scripts/a11yScorecard.mjs @@ -0,0 +1,550 @@ +/* eslint-disable no-console */ +/** + * Aggregates the per-component conformance reports into a library-level scorecard. + * + * Each `packages/mui-material/src//accessibility.md` opens with a + * count table rating the component against WCAG 2.2 Level A and AA. This script + * parses those reports and rewrites three generated outputs: + * + * - the `Reports` table in `packages/mui-material/src/accessibility.md` + * - the summary table on the public conformance page + * - `docs/data/material/getting-started/accessibility/scorecard.json`, the + * machine-readable rollup behind both. + * + * Run with `pnpm a11y:scorecard`; `--check` exits non-zero when either output is + * stale, so CI can guard against a report landing without its rollup. + */ +import fs from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as prettier from 'prettier'; + +const currentDirectory = path.dirname(fileURLToPath(import.meta.url)); +const rootDirectory = path.resolve(currentDirectory, '..'); +const componentsDirectory = path.join(rootDirectory, 'packages/mui-material/src'); +const indexPath = path.join(componentsDirectory, 'accessibility.md'); +const scorecardPath = path.join( + rootDirectory, + 'docs/data/material/getting-started/accessibility/scorecard.json', +); +const docsPagePath = path.join( + rootDirectory, + 'docs/data/material/getting-started/accessibility/accessibility.md', +); +const checklistPath = path.join(componentsDirectory, 'manual-testing.md'); + +const START_MARKER = ''; +const END_MARKER = ''; + +/** + * Rows are `|