From 877ab6af43af858e0a5236d7e65e5d9d3fc24caf Mon Sep 17 00:00:00 2001 From: michel Date: Sat, 24 Oct 2026 09:51:38 +0200 Subject: [PATCH 1/2] [test][form label] Add axe tests and WCAG conformance report Co-Authored-By: Claude Opus 5.5 --- .../form-label/form-label.a11y.json | 122 +++++++ .../accessibility/accessibility.md | 24 +- .../accessibility/knownGaps.json | 10 + .../accessibility/scorecard.json | 177 ++++++++- .../src/FormLabel/FormLabel.test.js | 95 ++++- .../src/FormLabel/accessibility.json | 21 ++ .../src/FormLabel/accessibility.md | 339 ++++++++++++++++++ packages/mui-material/src/accessibility.md | 1 + packages/mui-material/src/manual-testing.md | 11 + test/regressions/a11y/criteriaSuites.mjs | 5 + .../form-label/FormLabelA11yColorMatrix.js | 20 ++ .../FormLabelA11yColorMatrixDark.js | 16 + .../form-label/FormLabelA11yStates.js | 47 +++ test/regressions/demoMeta.test.ts | 13 + test/regressions/demoMeta.ts | 15 + 15 files changed, 896 insertions(+), 20 deletions(-) create mode 100644 docs/data/material/components/form-label/form-label.a11y.json create mode 100644 packages/mui-material/src/FormLabel/accessibility.json create mode 100644 packages/mui-material/src/FormLabel/accessibility.md create mode 100644 test/regressions/a11y/fixtures/form-label/FormLabelA11yColorMatrix.js create mode 100644 test/regressions/a11y/fixtures/form-label/FormLabelA11yColorMatrixDark.js create mode 100644 test/regressions/a11y/fixtures/form-label/FormLabelA11yStates.js diff --git a/docs/data/material/components/form-label/form-label.a11y.json b/docs/data/material/components/form-label/form-label.a11y.json new file mode 100644 index 00000000000000..52e70719ff4bfb --- /dev/null +++ b/docs/data/material/components/form-label/form-label.a11y.json @@ -0,0 +1,122 @@ +{ + "FormLabelA11yColorMatrix": { + "rules": { + "aria-allowed-attr": { + "status": "pass", + "tags": ["wcag2a"] + }, + "aria-conditional-attr": { + "status": "pass", + "tags": ["wcag2a"] + }, + "aria-prohibited-attr": { + "status": "pass", + "tags": ["wcag2a"] + }, + "aria-valid-attr": { + "status": "pass", + "tags": ["wcag2a"] + }, + "aria-valid-attr-value": { + "status": "pass", + "tags": ["wcag2a"] + }, + "color-contrast": { + "status": "fail", + "tags": ["wcag2aa"] + } + } + }, + "FormLabelA11yColorMatrixDark": { + "rules": { + "aria-allowed-attr": { + "status": "pass", + "tags": ["wcag2a"] + }, + "aria-conditional-attr": { + "status": "pass", + "tags": ["wcag2a"] + }, + "aria-prohibited-attr": { + "status": "pass", + "tags": ["wcag2a"] + }, + "aria-valid-attr": { + "status": "pass", + "tags": ["wcag2a"] + }, + "aria-valid-attr-value": { + "status": "pass", + "tags": ["wcag2a"] + }, + "color-contrast": { + "status": "pass", + "tags": ["wcag2aa"] + } + } + }, + "FormLabelA11yStates": { + "rules": { + "aria-allowed-attr": { + "status": "pass", + "tags": ["wcag2a"] + }, + "aria-conditional-attr": { + "status": "pass", + "tags": ["wcag2a"] + }, + "aria-deprecated-role": { + "status": "pass", + "tags": ["wcag2a"] + }, + "aria-hidden-focus": { + "status": "pass", + "tags": ["wcag2a"] + }, + "aria-prohibited-attr": { + "status": "pass", + "tags": ["wcag2a"] + }, + "aria-required-attr": { + "status": "pass", + "tags": ["wcag2a"] + }, + "aria-roles": { + "status": "pass", + "tags": ["wcag2a"] + }, + "aria-valid-attr": { + "status": "pass", + "tags": ["wcag2a"] + }, + "aria-valid-attr-value": { + "status": "pass", + "tags": ["wcag2a"] + }, + "color-contrast": { + "status": "pass", + "tags": ["wcag2aa"] + }, + "duplicate-id-aria": { + "status": "pass", + "tags": ["wcag2a"] + }, + "form-field-multiple-labels": { + "status": "pass", + "tags": ["wcag2a"] + }, + "label": { + "status": "pass", + "tags": ["wcag2a"] + }, + "nested-interactive": { + "status": "pass", + "tags": ["wcag2a"] + }, + "target-size": { + "status": "pass", + "tags": ["wcag22aa"] + } + } + } +} diff --git a/docs/data/material/getting-started/accessibility/accessibility.md b/docs/data/material/getting-started/accessibility/accessibility.md index ca08a4cef34293..8604b9ff2ebcca 100644 --- a/docs/data/material/getting-started/accessibility/accessibility.md +++ b/docs/data/material/getting-started/accessibility/accessibility.md @@ -3,7 +3,7 @@

How Material UI components conform to WCAG 2.2 Level A and AA, reported in VPAT terms for procurement and accessibility review.

:::warning -**Draft — partial coverage.** This report covers the 13 components assessed so far, not the whole library. It has not been reviewed by an external auditor, and no assistive-technology testing has been performed yet. See [Scope and limitations](#scope-and-limitations) before relying on it for a procurement decision. +**Draft — partial coverage.** This report covers only the components in the [component table](#conformance-by-component), not the whole library. It has not been reviewed by an external auditor, and no assistive-technology testing has been performed yet. See [Scope and limitations](#scope-and-limitations) before relying on it for a procurement decision. ::: ## About this report @@ -43,6 +43,7 @@ Each row counts only the criteria that **apply** to that component; criteria tha | [Button](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Button/accessibility.md) | 15 | 12 | 27 | 24 | 3 | 20/27 | 11 | | [Checkbox](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Checkbox/accessibility.md) | 14 | 11 | 25 | 24 | 1 | 22/25 | 11 | | [CircularProgress](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/CircularProgress/accessibility.md) | 6 | 5 | 11 | 8 | 3 | 6/11 | 1 | +| [FormLabel](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/FormLabel/accessibility.md) | 7 | 7 | 14 | 12 | 2 | 11/14 | 4 | | [LinearProgress](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/LinearProgress/accessibility.md) | 6 | 5 | 11 | 8 | 3 | 6/11 | 1 | | [Radio](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Radio/accessibility.md) | 14 | 11 | 25 | 24 | 1 | 23/25 | 11 | | [RadioGroup](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/RadioGroup/accessibility.md) | 6 | 1 | 7 | 7 | 0 | 4/7 | 2 | @@ -57,7 +58,7 @@ Each row counts only the criteria that **apply** to that component; criteria tha **No component records a ❌ Does Not Support rating for any Level A or AA criterion.** -Rolled up to the library level, where each criterion takes the worst rating any assessed component receives, 32 success criteria are exercised: **27 Supports, 5 Partially Supports, 0 Does Not Support.** +Rolled up to the library level, where each criterion takes the worst rating any assessed component receives, 32 success criteria are exercised: **26 Supports, 6 Partially Supports, 0 Does Not Support.** @@ -80,17 +81,18 @@ An application built with Material UI is not automatically accessible. Material ## Known gaps -Five issues account for every ⚠️ rating. Two of them can be fixed through the theme today. +Each ⚠️ rating comes from one of the issues below. Some of them can be fixed through the theme today. -| Gap | Criteria | Affected | Workaround | -| :------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------- | -| **By default, the keyboard focus indicator is the ripple or a background tint, with untested contrast** | [1.4.11 Non-text Contrast](https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html) | [AccordionSummary](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/AccordionSummary/accessibility.md)
[Button](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Button/accessibility.md)
[Checkbox](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Checkbox/accessibility.md)
[Radio](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Radio/accessibility.md)
[Switch](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Switch/accessibility.md)
[ToggleButton](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/ToggleButton/accessibility.md) | Enable `focusVisible` in your theme — see below | -| **Some default palette colors fall short of contrast minimums** | [1.4.3 Contrast (Minimum)](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html)
[1.4.11 Non-text Contrast](https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html) | [Avatar](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Avatar/accessibility.md)
[Button](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Button/accessibility.md)
[CircularProgress](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/CircularProgress/accessibility.md) (`warning` with `enableTrackSlot`)
[LinearProgress](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/LinearProgress/accessibility.md)
[Switch](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Switch/accessibility.md)
[TextField](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/TextField/accessibility.md)
[ToggleButton](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/ToggleButton/accessibility.md) | Override the affected palette entries | -| **The selected state of colored Toggle Buttons is conveyed almost entirely by hue** | [1.4.1 Use of Color](https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html) | [ToggleButton](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/ToggleButton/accessibility.md) (`primary`, `error`, `info`, `success`) | Add a non-color cue to the selected state, such as an icon | -| **Dynamic state changes are not announced** | [4.1.3 Status Messages](https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html) | [Button](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Button/accessibility.md) (`loading`)
[CircularProgress](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/CircularProgress/accessibility.md)
[LinearProgress](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/LinearProgress/accessibility.md)
[TextField](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/TextField/accessibility.md) (error) | Render your own `aria-live` region alongside | -| **Indefinite animation cannot be paused** | [2.2.2 Pause, Stop, Hide](https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html) | [CircularProgress](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/CircularProgress/accessibility.md) (`indeterminate`)
[LinearProgress](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/LinearProgress/accessibility.md) (`indeterminate`, `query`, `buffer`) | Show it only while an operation is in flight | +| Gap | Criteria | Affected | Workaround | +| :-------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ | +| **By default, the keyboard focus indicator is the ripple or a background tint, with untested contrast** | [1.4.11 Non-text Contrast](https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html) | [AccordionSummary](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/AccordionSummary/accessibility.md)
[Button](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Button/accessibility.md)
[Checkbox](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Checkbox/accessibility.md)
[Radio](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Radio/accessibility.md)
[Switch](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Switch/accessibility.md)
[ToggleButton](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/ToggleButton/accessibility.md) | Enable `focusVisible` in your theme — see below | +| **Some default palette colors fall short of contrast minimums** | [1.4.3 Contrast (Minimum)](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html)
[1.4.11 Non-text Contrast](https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html) | [Avatar](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Avatar/accessibility.md)
[Button](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Button/accessibility.md)
[CircularProgress](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/CircularProgress/accessibility.md) (`warning` with `enableTrackSlot`)
[FormLabel](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/FormLabel/accessibility.md) (focused `info`, `warning`; dark-mode `error` in a Dialog)
[LinearProgress](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/LinearProgress/accessibility.md)
[Switch](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Switch/accessibility.md)
[TextField](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/TextField/accessibility.md)
[ToggleButton](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/ToggleButton/accessibility.md) | Override the affected palette entries | +| **The selected state of colored Toggle Buttons is conveyed almost entirely by hue** | [1.4.1 Use of Color](https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html) | [ToggleButton](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/ToggleButton/accessibility.md) (`primary`, `error`, `info`, `success`) | Add a non-color cue to the selected state, such as an icon | +| **The required asterisk of a group legend is not exposed, because Checkbox, Radio, and Switch do not take `required` from FormControl** | [1.3.1 Info and Relationships](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html) | [FormLabel](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/FormLabel/accessibility.md) (`required` group legend) | Set `aria-required` on a RadioGroup, or `required` on a single Checkbox or Switch. For a checkbox group, state the requirement in the legend text | +| **Dynamic state changes are not announced** | [4.1.3 Status Messages](https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html) | [Button](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/Button/accessibility.md) (`loading`)
[CircularProgress](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/CircularProgress/accessibility.md)
[LinearProgress](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/LinearProgress/accessibility.md)
[TextField](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/TextField/accessibility.md) (error) | Render your own `aria-live` region alongside | +| **Indefinite animation cannot be paused** | [2.2.2 Pause, Stop, Hide](https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html) | [CircularProgress](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/CircularProgress/accessibility.md) (`indeterminate`)
[LinearProgress](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/LinearProgress/accessibility.md) (`indeterminate`, `query`, `buffer`) | Show it only while an operation is in flight | @@ -106,7 +108,7 @@ const theme = createTheme({ focusVisible: true }); Read this section before citing the report. ::: -- **Component coverage is partial.** 13 components are assessed. Widely used components including Select, Autocomplete, Dialog, Menu, Table, Tabs, Slider, Tooltip, Snackbar, and Drawer are **not yet assessed**, and this report says nothing about them. +- **Component coverage is partial.** Only the components in the [component table](#conformance-by-component) are assessed. Widely used components including Select, Autocomplete, Dialog, Menu, Table, Tabs, Slider, Tooltip, Snackbar, and Drawer are **not yet assessed**, and this report says nothing about them. - **No assistive-technology testing.** No screen-reader passes have been performed. Criteria that depend on how a specific assistive technology behaves are assessed from the exposed accessibility tree, not from observed behavior. - **Level AAA is out of scope**, as it is for a standard VPAT. - **Components are rated in isolation**, as rendered with default props and the default theme. Customization, composition, and your surrounding page can change the result. diff --git a/docs/data/material/getting-started/accessibility/knownGaps.json b/docs/data/material/getting-started/accessibility/knownGaps.json index 6062cc623e1f74..bcd9f8456f1f8a 100644 --- a/docs/data/material/getting-started/accessibility/knownGaps.json +++ b/docs/data/material/getting-started/accessibility/knownGaps.json @@ -12,6 +12,10 @@ "Avatar", "Button", { "component": "CircularProgress", "note": "`warning` with `enableTrackSlot`" }, + { + "component": "FormLabel", + "note": "focused `info`, `warning`; dark-mode `error` in a Dialog" + }, "LinearProgress", "Switch", "TextField", @@ -25,6 +29,12 @@ "affected": [{ "component": "ToggleButton", "note": "`primary`, `error`, `info`, `success`" }], "workaround": "Add a non-color cue to the selected state, such as an icon" }, + { + "gap": "The required asterisk of a group legend is not exposed, because Checkbox, Radio, and Switch do not take `required` from FormControl", + "criteria": ["1.3.1"], + "affected": [{ "component": "FormLabel", "note": "`required` group legend" }], + "workaround": "Set `aria-required` on a RadioGroup, or `required` on a single Checkbox or Switch. For a checkbox group, state the requirement in the legend text" + }, { "gap": "Dynamic state changes are not announced", "criteria": ["4.1.3"], diff --git a/docs/data/material/getting-started/accessibility/scorecard.json b/docs/data/material/getting-started/accessibility/scorecard.json index 5602791ebcc2b5..fc2f168005f5ed 100644 --- a/docs/data/material/getting-started/accessibility/scorecard.json +++ b/docs/data/material/getting-started/accessibility/scorecard.json @@ -1,12 +1,12 @@ { "standard": "WCAG 2.2 Level A and AA", - "componentCount": 13, + "componentCount": 14, "totals": { "rated": 32, - "supports": 27, - "partiallySupports": 5, + "supports": 26, + "partiallySupports": 6, "doesNotSupport": 0, - "flagged": 13 + "flagged": 14 }, "criteria": [ { @@ -21,9 +21,9 @@ "number": "1.3.1", "name": "Info and Relationships", "level": "A", - "conformance": "Supports", - "flagged": false, - "affected": [] + "conformance": "Partially Supports", + "flagged": true, + "affected": ["FormLabel"] }, { "number": "1.3.2", @@ -63,7 +63,7 @@ "level": "AA", "conformance": "Partially Supports", "flagged": true, - "affected": ["Avatar", "Button", "TextField", "ToggleButton"] + "affected": ["Avatar", "Button", "FormLabel", "TextField", "ToggleButton"] }, { "number": "1.4.4", @@ -1538,6 +1538,167 @@ "automated": 1 } }, + { + "component": "FormLabel", + "counts": { + "supports": 12, + "partiallySupports": 2, + "doesNotSupport": 0, + "notApplicable": 41, + "flagged": 3, + "rated": 14 + }, + "gaps": [ + { + "number": "1.3.1", + "name": "Info and Relationships", + "conformance": "Partially Supports" + }, + { + "number": "1.4.3", + "name": "Contrast (Minimum)", + "conformance": "Partially Supports" + } + ], + "criteria": [ + { + "number": "1.3.1", + "name": "Info and Relationships", + "level": "A", + "conformance": "Partially Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": true + }, + { + "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": "Hybrid", + "flagged": true + }, + { + "number": "1.4.3", + "name": "Contrast (Minimum)", + "level": "AA", + "conformance": "Partially Supports", + "responsibility": "Component", + "group": "Hybrid", + "flagged": false + }, + { + "number": "1.4.4", + "name": "Resize Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "1.4.5", + "name": "Images of Text", + "level": "AA", + "conformance": "Supports", + "responsibility": "Author", + "group": "Manual", + "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.4.6", + "name": "Headings and Labels", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "number": "2.5.3", + "name": "Label in Name", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "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": "Shared", + "group": "Hybrid", + "flagged": true + }, + { + "number": "4.1.2", + "name": "Name, Role, Value", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + } + ], + "summary": { + "rated": 14, + "levelA": 7, + "levelAA": 7, + "supports": 12, + "partiallySupports": 2, + "doesNotSupport": 0, + "verified": 11, + "automated": 4 + } + }, { "component": "LinearProgress", "counts": { diff --git a/packages/mui-material/src/FormLabel/FormLabel.test.js b/packages/mui-material/src/FormLabel/FormLabel.test.js index d0233b0925a640..ffe8c8049df055 100644 --- a/packages/mui-material/src/FormLabel/FormLabel.test.js +++ b/packages/mui-material/src/FormLabel/FormLabel.test.js @@ -4,9 +4,13 @@ import PropTypes from 'prop-types'; import { act, createRenderer, screen } from '@mui/internal-test-utils'; import FormLabel, { formLabelClasses as classes } from '@mui/material/FormLabel'; import FormControl, { useFormControl } from '@mui/material/FormControl'; -import { hexToRgb } from '@mui/material/styles'; +import Checkbox from '@mui/material/Checkbox'; +import Input from '@mui/material/Input'; +import { createTheme, hexToRgb } from '@mui/material/styles'; import defaultTheme from '../styles/defaultTheme'; import describeConformance from '../../test/describeConformance'; +import { contrastRatio, WCAG_MINIMUM_RATIO } from '../../test/contrast'; +import { FAILING_PALETTE_COLORS, PALETTE_CONTRAST_COLORS } from '../../test/contrastContract'; describe('', () => { const { render } = createRenderer(); @@ -188,4 +192,93 @@ describe('', () => { }); }); }); + + describe('WCAG 2.2 conformance', () => { + describe('1.3.1 Info and Relationships', () => { + it('names a fieldset group when rendered as its legend', () => { + render( + + Notification method + + , + ); + + expect(screen.getByRole('group')).toHaveAccessibleName('Notification method'); + }); + + it('names a field through htmlFor', () => { + render( + + Full name + + , + ); + + expect(screen.getByRole('textbox')).toHaveAccessibleName('Full name'); + }); + }); + + it('1.4.3 Contrast (Minimum): focused labels fall short of 4.5:1 only in the documented palette colors', () => { + const theme = createTheme(); + render( + + {PALETTE_CONTRAST_COLORS.map((color) => ( + + {color} + + ))} + , + ); + + // A focused label is `main` on the page background. + const failing = PALETTE_CONTRAST_COLORS.filter((color) => { + const { color: textColor } = window.getComputedStyle(screen.getByText(color)); + return ( + contrastRatio(textColor, theme.palette.background.default) < WCAG_MINIMUM_RATIO.normalText + ); + }); + expect(failing).to.deep.equal([...FAILING_PALETTE_COLORS]); + }); + + it('2.5.3 Label in Name: the accessible name of the field is the visible label', () => { + const { container } = render( + + Email address + + , + ); + + // The hidden asterisk adds nothing to the name. + expect(container.querySelector(`.${classes.asterisk}`)).to.have.attribute( + 'aria-hidden', + 'true', + ); + expect(screen.getByRole('textbox')).toHaveAccessibleName('Email address'); + }); + + describe('3.3.2 Labels or Instructions', () => { + it('passes the required state to an Input inside the FormControl', () => { + render( + + Full name + + , + ); + + expect(screen.getByRole('textbox')).to.have.attribute('required'); + }); + + it('does not pass the required state to a Checkbox (documented gap)', () => { + render( + + Terms + + , + ); + + // When this fails, the gap is fixed: update FormLabel/accessibility.md and knownGaps.json. + expect(screen.getByRole('checkbox')).not.to.have.attribute('required'); + }); + }); + }); }); diff --git a/packages/mui-material/src/FormLabel/accessibility.json b/packages/mui-material/src/FormLabel/accessibility.json new file mode 100644 index 00000000000000..a92ddec2f2f64f --- /dev/null +++ b/packages/mui-material/src/FormLabel/accessibility.json @@ -0,0 +1,21 @@ +{ + "axe": "docs/data/material/components/form-label/form-label.a11y.json", + "criteria": { + "1.3.1": { "conformance": "Partially Supports", "flagged": true }, + "1.4.3": { "responsibility": "Component" }, + "3.3.2": { "flagged": true } + }, + "notApplicable": [ + "1.1.1", + "1.4.11", + "2.1.1", + "2.1.2", + "2.4.3", + "2.4.7", + "2.4.11", + "2.5.2", + "2.5.8", + "3.2.1", + "3.2.2" + ] +} diff --git a/packages/mui-material/src/FormLabel/accessibility.md b/packages/mui-material/src/FormLabel/accessibility.md new file mode 100644 index 00000000000000..401c0b321f28d5 --- /dev/null +++ b/packages/mui-material/src/FormLabel/accessibility.md @@ -0,0 +1,339 @@ + + +# FormLabel accessibility conformance + +Rated against WCAG 2.2 Level A and AA. See the [reports legend](../accessibility.md). + + + +FormLabel labels a field or, as a `legend`, names a group. It is not focusable. A click on a label with `htmlFor` focuses or activates its field, so the field report rates the keyboard, focus, and pointer criteria. + + + +| Result | Count | +| :-------------------- | :---- | +| ✅ Supports | 12 | +| ⚠️ Partially Supports | 2 | +| ❌ Does Not Support | 0 | +| ➖ Not Applicable | 41 | +| 🚩 Flagged | 3/14 | + +## Known gaps + +- ⚠️ **1.3.1 Info and Relationships.** On a `required` group legend, the asterisk is `aria-hidden`, and Checkbox, Radio, and Switch do not take `required` from FormControl. Assistive technology then gets no required state. +- ⚠️ **1.4.3 Contrast (Minimum).** A focused label takes the `main` color of its field. In light mode, `info` (`3.86:1`) and `warning` (`3.11:1`) are less than `4.5:1`. In dark mode, the error label is about `3.1:1` in a Dialog, because the elevation overlay lightens the surface. + +## Success criteria + +### 🔍 Manual + +#### 1.3.2 Meaningful Sequence · A + +`✅ Supports` · `○ Author` + + + +- The label renders its text, then the asterisk when `required`. The asterisk is `aria-hidden`. + +**Manual testing steps** + +1. Read a form with a screen reader in browse mode. +2. Compare the reading order of each label and its field with the visual order. + + + +**Pass:** each label comes before its field in the reading order. + +#### 1.3.3 Sensory Characteristics · A + +`✅ Supports` · `○ Author` + + + +- The label is text. Instructions in the page must not refer to a field only by its position or color. + +**Manual testing steps** + +1. Find product copy that refers to a form field. +2. Make sure that it names the field by its label. + + + +**Pass:** each instruction names the field by its label. + +#### 1.4.5 Images of Text · AA + +`✅ Supports` · `○ Author` + + + +- The label is real text. + +**Manual testing steps** + +1. Select the text of a label with the mouse. + + + +**Pass:** the mouse selects the label text character by character. + +#### 3.2.4 Consistent Identification · AA + +`✅ Supports` · `○ Author` + + + +- The label text identifies the field. Consistency across pages is an authoring concern. + +**Manual testing steps** + +1. Find fields with the same function on different pages. +2. Compare their labels. + + + +**Pass:** fields with the same function have the same label. + +### 🔁 Hybrid + +#### 1.3.1 Info and Relationships · A + +`🚩` · `⚠️ Partially Supports` · `◐ Shared` + +Tested by: unit `FormLabel.test.js` + + + +- The root is a `