From 64fdebbe4b09e7aa24456465dd4e1028e9fdfdca Mon Sep 17 00:00:00 2001 From: michel Date: Sat, 24 Oct 2026 09:52:02 +0200 Subject: [PATCH 1/2] [test][form group] Add axe tests and WCAG conformance report Co-Authored-By: Claude Opus 5.5 --- .../form-group/form-group.a11y.json | 66 +++++ .../accessibility/accessibility.md | 5 +- .../accessibility/scorecard.json | 125 ++++++++- .../src/FormGroup/FormGroup.test.js | 87 +++++- .../src/FormGroup/accessibility.json | 25 ++ .../src/FormGroup/accessibility.md | 264 ++++++++++++++++++ packages/mui-material/src/accessibility.md | 1 + packages/mui-material/src/manual-testing.md | 11 + test/regressions/a11y/criteriaSuites.mjs | 5 + .../form-group/FormGroupA11yStates.js | 67 +++++ test/regressions/demoMeta.test.ts | 10 + test/regressions/demoMeta.ts | 7 + 12 files changed, 669 insertions(+), 4 deletions(-) create mode 100644 docs/data/material/components/form-group/form-group.a11y.json create mode 100644 packages/mui-material/src/FormGroup/accessibility.json create mode 100644 packages/mui-material/src/FormGroup/accessibility.md create mode 100644 test/regressions/a11y/fixtures/form-group/FormGroupA11yStates.js diff --git a/docs/data/material/components/form-group/form-group.a11y.json b/docs/data/material/components/form-group/form-group.a11y.json new file mode 100644 index 00000000000000..ed7555b1c5cbca --- /dev/null +++ b/docs/data/material/components/form-group/form-group.a11y.json @@ -0,0 +1,66 @@ +{ + "FormGroupA11yStates": { + "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..666f80405c7bb7 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 | +| [FormGroup](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/FormGroup/accessibility.md) | 7 | 4 | 11 | 11 | 0 | 7/11 | 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 | @@ -106,7 +107,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/scorecard.json b/docs/data/material/getting-started/accessibility/scorecard.json index 5602791ebcc2b5..b5255b9928cc99 100644 --- a/docs/data/material/getting-started/accessibility/scorecard.json +++ b/docs/data/material/getting-started/accessibility/scorecard.json @@ -1,6 +1,6 @@ { "standard": "WCAG 2.2 Level A and AA", - "componentCount": 13, + "componentCount": 14, "totals": { "rated": 32, "supports": 27, @@ -1538,6 +1538,129 @@ "automated": 1 } }, + { + "component": "FormGroup", + "counts": { + "supports": 11, + "partiallySupports": 0, + "doesNotSupport": 0, + "notApplicable": 44, + "flagged": 4, + "rated": 11 + }, + "gaps": [], + "criteria": [ + { + "number": "1.3.1", + "name": "Info and Relationships", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Hybrid", + "flagged": false + }, + { + "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.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.4.3", + "name": "Focus Order", + "level": "A", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Automated", + "flagged": false + }, + { + "number": "2.4.6", + "name": "Headings and Labels", + "level": "AA", + "conformance": "Supports", + "responsibility": "Shared", + "group": "Manual", + "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 + } + ], + "summary": { + "rated": 11, + "levelA": 7, + "levelAA": 4, + "supports": 11, + "partiallySupports": 0, + "doesNotSupport": 0, + "verified": 7, + "automated": 4 + } + }, { "component": "LinearProgress", "counts": { diff --git a/packages/mui-material/src/FormGroup/FormGroup.test.js b/packages/mui-material/src/FormGroup/FormGroup.test.js index a33b21f2b3a6ab..824caf5bc2c387 100644 --- a/packages/mui-material/src/FormGroup/FormGroup.test.js +++ b/packages/mui-material/src/FormGroup/FormGroup.test.js @@ -1,7 +1,9 @@ import { describe, it, expect } from 'vitest'; -import { createRenderer, screen } from '@mui/internal-test-utils'; +import * as React from 'react'; +import { createRenderer, screen, within, isJsdom } from '@mui/internal-test-utils'; import FormGroup, { formGroupClasses as classes } from '@mui/material/FormGroup'; import FormControl from '@mui/material/FormControl'; +import FormLabel from '@mui/material/FormLabel'; import describeConformance from '../../test/describeConformance'; describe('', () => { @@ -42,4 +44,87 @@ describe('', () => { }); }); }); + + describe('WCAG 2.2 conformance', () => { + describe('1.3.1 Info and Relationships', () => { + it('renders no role of its own', () => { + const { container } = render( + + + , + ); + + expect(container.firstChild).not.to.have.attribute('role'); + expect(screen.queryByRole('group')).to.equal(null); + }); + + it('gets its group name from a fieldset legend', () => { + render( + + Toppings + + + + , + ); + + const group = screen.getByRole('group', { name: 'Toppings' }); + expect(within(group).getByRole('checkbox', { name: 'Cheese' })).not.to.equal(null); + }); + + it('accepts role="group" with aria-labelledby', () => { + render( + + Share with + + + + , + ); + + expect(screen.getByRole('group')).toHaveAccessibleName('Share with'); + }); + }); + + // JSDOM has no layout + it.skipIf(isJsdom())( + '2.4.3 Focus Order: a row group shows its controls from left to right in tab order', + async () => { + const { user } = render( + + + + + , + ); + + let previousLeft = -Infinity; + for (const name of ['Email', 'Push', 'Text message']) { + // eslint-disable-next-line no-await-in-loop + await user.tab(); + const checkbox = screen.getByRole('checkbox', { name }); + expect(checkbox).toHaveFocus(); + const { left } = checkbox.getBoundingClientRect(); + expect(left).to.be.above(previousLeft); + previousLeft = left; + } + }, + ); + + it('2.4.3 Focus Order: the controls of a row group are tab stops in DOM order', async () => { + const { user } = render( + + + + + , + ); + + for (const name of ['Email', 'Push', 'Text message']) { + // eslint-disable-next-line no-await-in-loop + await user.tab(); + expect(screen.getByRole('checkbox', { name })).toHaveFocus(); + } + }); + }); }); diff --git a/packages/mui-material/src/FormGroup/accessibility.json b/packages/mui-material/src/FormGroup/accessibility.json new file mode 100644 index 00000000000000..b69bb9076b8882 --- /dev/null +++ b/packages/mui-material/src/FormGroup/accessibility.json @@ -0,0 +1,25 @@ +{ + "axe": "docs/data/material/components/form-group/form-group.a11y.json", + "criteria": { + "1.4.1": { "group": "Manual" }, + "1.3.2": { "responsibility": "Shared", "flagged": true }, + "2.4.6": { "group": "Manual", "flagged": true }, + "3.3.2": { "flagged": true } + }, + "notApplicable": [ + "1.1.1", + "1.4.3", + "1.4.5", + "1.4.11", + "2.1.1", + "2.1.2", + "2.4.7", + "2.4.11", + "2.5.2", + "2.5.3", + "2.5.8", + "3.2.1", + "3.2.2", + "3.2.4" + ] +} diff --git a/packages/mui-material/src/FormGroup/accessibility.md b/packages/mui-material/src/FormGroup/accessibility.md new file mode 100644 index 00000000000000..899492a1d32685 --- /dev/null +++ b/packages/mui-material/src/FormGroup/accessibility.md @@ -0,0 +1,264 @@ + + +# FormGroup accessibility conformance + +Rated against WCAG 2.2 Level A and AA. See the [reports legend](../accessibility.md). + + + +FormGroup arranges a set of checkboxes or switches. It renders a `div` with no role. The group semantics come from a `fieldset` with a legend, or from `role="group"` with a name. For radio buttons, use RadioGroup. + + + +| Result | Count | +| :-------------------- | :---- | +| ✅ Supports | 11 | +| ⚠️ Partially Supports | 0 | +| ❌ Does Not Support | 0 | +| ➖ Not Applicable | 44 | +| 🚩 Flagged | 4/11 | + +## Known gaps + +None. + +## Success criteria + +### 🔍 Manual + +#### 1.3.2 Meaningful Sequence · A + +`🚩` · `✅ Supports` · `◐ Shared` + + + +- FormGroup shows its controls in DOM order, in a column, or in a row with `row`. It does not reverse the order. + +**Manual testing steps** + +1. Start a screen reader. +2. Read a FormGroup with `row` with the reading keys (Down Arrow in NVDA, VO+Right Arrow in VoiceOver). +3. Compare the reading order with the visual order. + + + +**Pass:** the reading order matches the visual order. + +#### 1.3.3 Sensory Characteristics · A + +`✅ Supports` · `○ Author` + + + +- Instructions for a group must not rely on its position or color alone. This is an authoring concern. + +**Manual testing steps** + +1. Find product copy that refers to a group of options. +2. Make sure that it names the group by its label. + + + +**Pass:** each instruction names the group by its label. + +#### 1.4.1 Use of Color · A + +`🚩` · `✅ Supports` · `◐ Shared` + + + +- The built-in error cues are color only: the legend and the helper text turn red. FormGroup gets the `error` class but no visual style of its own. +- The message text must name the error, so that it is clear without color. The author writes it. In the CheckboxesGroup docs demo, the helper text says "You can display an error" in both states, so only the color changes. + +**Manual testing steps** + +1. Put a group in the error state. +2. In Chrome DevTools, open the Rendering tab, and select Emulate vision deficiencies > Achromatopsia. +3. Read the message text. + + + +**Pass:** the message text names the error without color, for example "Error: select at least one delivery day." + +#### 2.4.6 Headings and Labels · AA + +`🚩` · `✅ Supports` · `◐ Shared` + + + +- The legend or the `aria-labelledby` element names the group. Whether the name describes the group is an authoring concern. + +**Manual testing steps** + +1. List the name of each group. +2. Read each name without its options. + + + +**Pass:** each group name tells the purpose of the set, for example "Toppings". + +### 🔁 Hybrid + +#### 1.3.1 Info and Relationships · A + +`✅ Supports` · `◐ Shared` + +Tested by: unit `FormGroup.test.js` + + + +- FormGroup renders a `div` with no role, so by itself it does not tell assistive technology that the controls form a group. A unit test confirms this. +- Inside `FormControl component="fieldset"` with `FormLabel component="legend"`, the `fieldset` is the group, and the legend is its name. With `role="group"` and `aria-labelledby` on FormGroup, FormGroup itself is a named group. Unit tests confirm both patterns. +- A group without a `fieldset` or a role has no name. This is the author's responsibility. +- FormControl does not connect a FormHelperText to the group. The author adds `aria-describedby`. In the error state, the checkboxes get no `aria-invalid`, because Checkbox and Switch take only `disabled` from FormControl. +- ARIA does not allow `aria-label` on an element with no role, and assistive technology can ignore it. axe-core `aria-prohibited-attr` reports it for review. The FormControlLabelPosition docs demos use this pattern. + +**Manual testing steps** + +1. Start a screen reader (NVDA with Chrome, or VoiceOver with Safari). In Safari, turn on "Press Tab to highlight each item". +2. In a FormGroup inside `FormControl component="fieldset"` with a legend, press Tab into the group. +3. Listen to the announcement. +4. Do the test again with a FormGroup that has no `fieldset`. + + + +**Pass:** the screen reader announces the group name once when focus enters the group. Without a `fieldset` or a role, the group has no name, and the author must add one. + +#### 3.3.2 Labels or Instructions · A + +`🚩` · `✅ Supports` · `◐ Shared` + +Tested by: axe-core `form-field-multiple-labels` ✅ + + + +- The legend, or the element that `aria-labelledby` names, is the visible label of the group. FormGroup adds no label of its own. +- The legend asterisk of a required group is visual only. The group does not expose a required state, and a `fieldset` cannot carry `aria-required`. Write the rule in text, for example "Select at least one". +- Result from the checkbox, switch, app bar, and list demos: CheckboxesGroup, SwitchesGroup, and both FormControlLabelPosition demos have a group label. CheckboxLabels, SwitchLabels, CustomizedSwitches, MenuAppBar, and InteractiveList have none, but their controls are separate options, and each control has its own label. + +**Manual testing steps** + +1. Check that each checkbox and switch group that asks one question has a visible label. +2. Check that the instructions say how many options the user must select. + + + +**Pass:** each group that asks one question has a visible label and clear instructions. + +#### 4.1.2 Name, Role, Value · A + +`✅ Supports` · `◐ Shared` + +Tested by: axe-core `aria-allowed-attr` ✅, `aria-conditional-attr` ✅, `aria-deprecated-role` ✅, `aria-hidden-focus` ✅, `aria-prohibited-attr` ✅, `aria-required-attr` ✅, `aria-roles` ✅, `aria-valid-attr` ✅, `aria-valid-attr-value` ✅, `duplicate-id-aria` ✅, `label` ✅, `nested-interactive` ✅ + + + +- FormGroup has no role, name, or value of its own. The controls inside it expose theirs. +- When used as documented, the container exposes the group role and a name: the `fieldset`, or `role="group"` on FormGroup. axe-core ARIA rules pass on the fixture. + +**Manual testing steps** + +1. With a screen reader, press Tab into a named group. +2. Listen to the group role and name, and the name and state of the first control. + + + +**Pass:** the screen reader announces the group and its name, then the control with its name and state. + +### ⚙️ Automated + +#### 1.4.4 Resize Text · AA + +`✅ Supports` · `◐ Shared` + +Tested by: Playwright + + + +- The controls and their labels scale with the text size, and the group wraps them. A Playwright test finds no horizontal scroll or clipping in the states fixture at a 200% text size. + + + +#### 1.4.10 Reflow · AA + +`✅ Supports` · `◐ Shared` + +Tested by: Playwright + + + +- The group has `flex-wrap: wrap`, so a row of controls wraps onto more lines. A Playwright test confirms that a row of five checkboxes does not scroll horizontally at 320 CSS px. + + + +#### 1.4.12 Text Spacing · AA + +`✅ Supports` · `◐ Shared` + +Tested by: Playwright + + + +- The group has no fixed height. A Playwright test applies the WCAG text-spacing values to the states fixture and finds no clipped text. + + + +#### 2.4.3 Focus Order · A + +`✅ Supports` · `◐ Shared` + +Tested by: unit `FormGroup.test.js` + + + +- The controls are tab stops in DOM order, also with `row`. A unit test confirms this. A second test, in a real browser, confirms that a row group shows its controls from left to right in tab order. + + + +## Not applicable + +- **1.1.1 Non-text Content (A).** FormGroup renders no content of its own. +- **1.2.1 Audio-only and Video-only (Prerecorded) (A), 1.2.2 Captions (Prerecorded) (A), 1.2.3 Audio Description or Media Alternative (Prerecorded) (A), 1.2.4 Captions (Live) (AA), 1.2.5 Audio Description (Prerecorded) (AA).** No audio or video. +- **1.3.4 Orientation (AA).** Sets no orientation lock; a layout concern. +- **1.3.5 Identify Input Purpose (AA).** Collects no information about the user through `autocomplete`. +- **1.4.2 Audio Control (A).** Emits no audio. +- **1.4.3 Contrast (Minimum) (AA), 1.4.5 Images of Text (AA).** FormGroup renders no text of its own. The legend and the labels own these criteria. +- **1.4.11 Non-text Contrast (AA).** FormGroup renders no graphical object and no component boundary. +- **1.4.13 Content on Hover or Focus (AA).** Shows no additional content on hover or focus; a `Tooltip` wrapper would own this. +- **2.1.1 Keyboard (A), 2.1.2 No Keyboard Trap (A), 2.4.7 Focus Visible (AA), 2.4.11 Focus Not Obscured (Minimum) (AA), 2.5.2 Pointer Cancellation (A), 2.5.3 Label in Name (A), 2.5.8 Target Size (Minimum) (AA), 3.2.1 On Focus (A), 3.2.2 On Input (A), 3.2.4 Consistent Identification (AA).** FormGroup is not focusable and has no action of its own. The controls inside it cover these. +- **2.1.4 Character Key Shortcuts (A).** Binds no single-character keyboard shortcut. +- **2.2.1 Timing Adjustable (A).** Sets no time limit. +- **2.2.2 Pause, Stop, Hide (A).** No auto-starting moving, blinking, or auto-updating content. +- **2.3.1 Three Flashes or Below Threshold (A).** Nothing flashes. +- **2.4.1 Bypass Blocks (A), 2.4.2 Page Titled (A), 2.4.5 Multiple Ways (AA).** Page or site structure, not a single component. +- **2.4.4 Link Purpose (In Context) (A).** The component renders no links. +- **2.5.1 Pointer Gestures (A), 2.5.4 Motion Actuation (A).** Activates on a simple click; reads no device motion. +- **2.5.7 Dragging Movements (AA).** No drag interactions. +- **3.1.1 Language of Page (A), 3.1.2 Language of Parts (AA).** The component emits no `` and no text of its own. A foreign-language label is the author's phrase to mark. +- **3.2.3 Consistent Navigation (AA).** Covers repeated navigation across a set of pages, not a single component. +- **3.2.6 Consistent Help (A).** Covers consistent placement of help across pages. +- **3.3.1 Error Identification (A), 3.3.3 Error Suggestion (AA), 3.3.4 Error Prevention (Legal, Financial, Data) (AA).** Collects and validates no input; these belong to the form or process. +- **3.3.7 Redundant Entry (A).** Captures no previously entered data to repopulate. +- **3.3.8 Accessible Authentication (Minimum) (AA).** No authentication step; the duty falls on the credential fields. +- **4.1.3 Status Messages (AA).** Emits no status messages; state changes are covered by 4.1.2. + +## Level AAA + + + +None. FormGroup adds no behavior that a Level AAA criterion tests. + + + +## Scope and test environment + +- **Standard.** WCAG 2.2, Level A and AA. +- **Component version.** `@mui/material` 10.0.0-alpha.0. + + + +- **Scope.** The FormGroup component, with and without a `fieldset`, rendered through its documented API. +- **Automated.** axe-core in the Playwright test harness, on the FormGroup fixture in [`form-group.a11y.json`](../../../../docs/data/material/components/form-group/form-group.a11y.json). Tests are in `FormGroup.test.js`. +- **Assistive-technology review.** Not done yet. The flagged criteria come from a review of the source. A review with NVDA, JAWS, and VoiceOver must still confirm them. + + diff --git a/packages/mui-material/src/accessibility.md b/packages/mui-material/src/accessibility.md index ccd0a626c6386c..99a98b9177ce7e 100644 --- a/packages/mui-material/src/accessibility.md +++ b/packages/mui-material/src/accessibility.md @@ -189,6 +189,7 @@ Edit the JSON files, not this table. | [Button](./Button/accessibility.md) | 24 | 3 | 0 | 28 | — | 7/27 | | [Checkbox](./Checkbox/accessibility.md) | 24 | 1 | 0 | 30 | — | 3/25 | | [CircularProgress](./CircularProgress/accessibility.md) | 8 | 3 | 0 | 44 | — | 5/11 | +| [FormGroup](./FormGroup/accessibility.md) | 11 | 0 | 0 | 44 | — | 4/11 | | [LinearProgress](./LinearProgress/accessibility.md) | 8 | 3 | 0 | 44 | — | 5/11 | | [Radio](./Radio/accessibility.md) | 24 | 1 | 0 | 30 | — | 2/25 | | [RadioGroup](./RadioGroup/accessibility.md) | 7 | 0 | 0 | 30 | 18 | 3/7 | diff --git a/packages/mui-material/src/manual-testing.md b/packages/mui-material/src/manual-testing.md index 4a34f0df831d0a..badc17dbd2ca1a 100644 --- a/packages/mui-material/src/manual-testing.md +++ b/packages/mui-material/src/manual-testing.md @@ -106,6 +106,17 @@ Run the rows matching what you touched, for the component you touched. - [ ] 🔁 **4.1.2 Name, Role, Value** — name, role, and value are correct for every variant, and the track is silent. - [ ] 🔍 **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. +## FormGroup + +6 checks · full procedures in [`FormGroup/accessibility.md`](./FormGroup/accessibility.md) + +- [ ] 🔁 **1.3.1 Info and Relationships** — the screen reader announces the group name once when focus enters the group. Without a `fieldset` or a role, the group has no name, and the author must add one. +- [ ] 🔍 **1.3.2 Meaningful Sequence** — the reading order matches the visual order. +- [ ] 🔍 **1.4.1 Use of Color** — the message text names the error without color, for example "Error: select at least one delivery day." +- [ ] 🔍 **2.4.6 Headings and Labels** — each group name tells the purpose of the set, for example "Toppings". +- [ ] 🔁 **3.3.2 Labels or Instructions** — each group that asks one question has a visible label and clear instructions. +- [ ] 🔁 **4.1.2 Name, Role, Value** — the screen reader announces the group and its name, then the control with its name and state. + ## LinearProgress 8 checks · full procedures in [`LinearProgress/accessibility.md`](./LinearProgress/accessibility.md) diff --git a/test/regressions/a11y/criteriaSuites.mjs b/test/regressions/a11y/criteriaSuites.mjs index c8fcde0fe3808c..a562dc8d402bfe 100644 --- a/test/regressions/a11y/criteriaSuites.mjs +++ b/test/regressions/a11y/criteriaSuites.mjs @@ -28,6 +28,11 @@ export const CSS_LAYOUT_SUITES = [ // The spinner renders no text, so the report rates 1.4.4 and 1.4.12 not applicable. skipCriteria: ['1.4.4', '1.4.12'], }, + { + // FormGroup has no docs page of its own, so the layout suite runs on its a11y fixture. + component: 'FormGroup', + route: '/a11y-form-group/FormGroupA11yStates', + }, { component: 'LinearProgress', route: '/docs-components-progress/LinearDeterminate', diff --git a/test/regressions/a11y/fixtures/form-group/FormGroupA11yStates.js b/test/regressions/a11y/fixtures/form-group/FormGroupA11yStates.js new file mode 100644 index 00000000000000..d435bd8229ad35 --- /dev/null +++ b/test/regressions/a11y/fixtures/form-group/FormGroupA11yStates.js @@ -0,0 +1,67 @@ +import Stack from '@mui/material/Stack'; +import Checkbox from '@mui/material/Checkbox'; +import FormControl from '@mui/material/FormControl'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import FormGroup from '@mui/material/FormGroup'; +import FormHelperText from '@mui/material/FormHelperText'; +import FormLabel from '@mui/material/FormLabel'; +import Switch from '@mui/material/Switch'; +import Typography from '@mui/material/Typography'; + +// FormGroup renders a div with no role. These groups get their name from a +// fieldset legend or from role="group" with aria-labelledby. The "Remember me" +// group has no name, which is the author's responsibility. The wide row wraps +// in the 320px layout suite. +export default function FormGroupA11yStates() { + return ( + + + Toppings + + } label="Cheese" /> + } label="Mushrooms" /> + + Pick any number. + + + Notifications + + } label="Email" /> + } label="Push" /> + + + + Delivery days + + } label="Monday" /> + } label="Tuesday" /> + + Error: select at least one delivery day. + +
+ Share with + + } label="Team" /> + } label="Company" /> + +
+ + } label="Remember me" /> + + + Interests + + {[ + 'Accessibility', + 'Design systems', + 'Performance', + 'Internationalization', + 'Testing', + ].map((label) => ( + } label={label} /> + ))} + + +
+ ); +} diff --git a/test/regressions/demoMeta.test.ts b/test/regressions/demoMeta.test.ts index 3b66852404ebe8..2bf63a70de01e4 100644 --- a/test/regressions/demoMeta.test.ts +++ b/test/regressions/demoMeta.test.ts @@ -137,6 +137,16 @@ describe('getConfig', () => { ).to.deep.include({ enabled: true, assertions: 'all' }); }); + it('asserts every rule on the FormGroup fixture', () => { + expect( + getConfig(A11Y_RULES, 'test/regressions/a11y/fixtures/form-group/FormGroupA11yStates'), + ).to.deep.equal({ + test: 'test/regressions/a11y/fixtures/form-group/FormGroupA11yStates', + enabled: true, + assertions: 'all', + }); + }); + it('returns undefined for a radio demo outside the enrolment', () => { // The radio-buttons enrolment omits FormControlLabelPlacement. expect( diff --git a/test/regressions/demoMeta.ts b/test/regressions/demoMeta.ts index 6469ebe98df752..053d1a40c1ddbd 100644 --- a/test/regressions/demoMeta.ts +++ b/test/regressions/demoMeta.ts @@ -406,6 +406,13 @@ export const A11Y_RULES: A11yRule[] = [ enabled: true, assertions: 'all', }, + // A11y-only fixture under `test/regressions/a11y/fixtures/form-group/`. + // FormGroup has no docs page, so the fixture owns the `form-group` slug. + { + test: 'test/regressions/a11y/fixtures/form-group/FormGroupA11yStates', + enabled: true, + assertions: 'all', + }, { test: `docs/data/material/components/radio-buttons/{${RADIO_A11Y_DEMOS.join(',')}}`, enabled: true, From 8802875221cc95520283b75141684668a1223336 Mon Sep 17 00:00:00 2001 From: michel Date: Thu, 29 Oct 2026 15:59:16 +0100 Subject: [PATCH 2/2] Address review feedback Co-Authored-By: Claude Opus 5.5 --- .../accessibility/accessibility.md | 2 +- .../accessibility/scorecard.json | 56 ++--------- .../src/FormGroup/FormGroup.test.js | 96 +++++++++---------- .../src/FormGroup/accessibility.json | 7 +- .../src/FormGroup/accessibility.md | 96 +++++-------------- packages/mui-material/src/accessibility.md | 2 +- packages/mui-material/src/manual-testing.md | 6 +- test/regressions/a11y/criteriaSuites.mjs | 2 + 8 files changed, 91 insertions(+), 176 deletions(-) diff --git a/docs/data/material/getting-started/accessibility/accessibility.md b/docs/data/material/getting-started/accessibility/accessibility.md index 666f80405c7bb7..55adfa3619dd48 100644 --- a/docs/data/material/getting-started/accessibility/accessibility.md +++ b/docs/data/material/getting-started/accessibility/accessibility.md @@ -43,7 +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 | -| [FormGroup](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/FormGroup/accessibility.md) | 7 | 4 | 11 | 11 | 0 | 7/11 | 4 | +| [FormGroup](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/FormGroup/accessibility.md) | 5 | 2 | 7 | 7 | 0 | 4/7 | 2 | | [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 | diff --git a/docs/data/material/getting-started/accessibility/scorecard.json b/docs/data/material/getting-started/accessibility/scorecard.json index b5255b9928cc99..dd6d376c0d4383 100644 --- a/docs/data/material/getting-started/accessibility/scorecard.json +++ b/docs/data/material/getting-started/accessibility/scorecard.json @@ -1541,12 +1541,12 @@ { "component": "FormGroup", "counts": { - "supports": 11, + "supports": 7, "partiallySupports": 0, "doesNotSupport": 0, - "notApplicable": 44, - "flagged": 4, - "rated": 11 + "notApplicable": 48, + "flagged": 3, + "rated": 7 }, "gaps": [], "criteria": [ @@ -1577,24 +1577,6 @@ "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.4", - "name": "Resize Text", - "level": "AA", - "conformance": "Supports", - "responsibility": "Shared", - "group": "Automated", - "flagged": false - }, { "number": "1.4.10", "name": "Reflow", @@ -1604,15 +1586,6 @@ "group": "Automated", "flagged": false }, - { - "number": "1.4.12", - "name": "Text Spacing", - "level": "AA", - "conformance": "Supports", - "responsibility": "Shared", - "group": "Automated", - "flagged": false - }, { "number": "2.4.3", "name": "Focus Order", @@ -1639,26 +1612,17 @@ "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": 11, - "levelA": 7, - "levelAA": 4, - "supports": 11, + "rated": 7, + "levelA": 5, + "levelAA": 2, + "supports": 7, "partiallySupports": 0, "doesNotSupport": 0, - "verified": 7, - "automated": 4 + "verified": 4, + "automated": 2 } }, { diff --git a/packages/mui-material/src/FormGroup/FormGroup.test.js b/packages/mui-material/src/FormGroup/FormGroup.test.js index 824caf5bc2c387..b3e67b1f6c2f15 100644 --- a/packages/mui-material/src/FormGroup/FormGroup.test.js +++ b/packages/mui-material/src/FormGroup/FormGroup.test.js @@ -45,45 +45,61 @@ describe('', () => { }); }); - describe('WCAG 2.2 conformance', () => { - describe('1.3.1 Info and Relationships', () => { - it('renders no role of its own', () => { - const { container } = render( + describe('accessibility', () => { + it('renders no role of its own', () => { + const { container } = render( + + + , + ); + + expect(container.firstChild).not.to.have.attribute('role'); + expect(screen.queryByRole('group')).to.equal(null); + }); + + it('gets its group name from a fieldset legend', () => { + render( + + Toppings - , - ); + + , + ); - expect(container.firstChild).not.to.have.attribute('role'); - expect(screen.queryByRole('group')).to.equal(null); - }); + const group = screen.getByRole('group', { name: 'Toppings' }); + expect(within(group).getByRole('checkbox', { name: 'Cheese' })).not.to.equal(null); + }); - it('gets its group name from a fieldset legend', () => { - render( - - Toppings - - - - , - ); + it('makes the controls of a row group tab stops in DOM order', async () => { + const { user } = render( + + + + + , + ); - const group = screen.getByRole('group', { name: 'Toppings' }); - expect(within(group).getByRole('checkbox', { name: 'Cheese' })).not.to.equal(null); - }); + for (const name of ['Email', 'Push', 'Text message']) { + // eslint-disable-next-line no-await-in-loop + await user.tab(); + expect(screen.getByRole('checkbox', { name })).toHaveFocus(); + } + }); + }); - it('accepts role="group" with aria-labelledby', () => { - render( - - Share with - - - - , - ); + describe('WCAG 2.2 conformance', () => { + it('1.3.1 Info and Relationships: becomes a named group with role="group" and aria-labelledby', () => { + render( + + Share with + + + + , + ); - expect(screen.getByRole('group')).toHaveAccessibleName('Share with'); - }); + expect(screen.getByRole('group')).toHaveAccessibleName('Share with'); }); // JSDOM has no layout @@ -110,21 +126,5 @@ describe('', () => { } }, ); - - it('2.4.3 Focus Order: the controls of a row group are tab stops in DOM order', async () => { - const { user } = render( - - - - - , - ); - - for (const name of ['Email', 'Push', 'Text message']) { - // eslint-disable-next-line no-await-in-loop - await user.tab(); - expect(screen.getByRole('checkbox', { name })).toHaveFocus(); - } - }); }); }); diff --git a/packages/mui-material/src/FormGroup/accessibility.json b/packages/mui-material/src/FormGroup/accessibility.json index b69bb9076b8882..dc3bd0ee456ddd 100644 --- a/packages/mui-material/src/FormGroup/accessibility.json +++ b/packages/mui-material/src/FormGroup/accessibility.json @@ -1,16 +1,18 @@ { "axe": "docs/data/material/components/form-group/form-group.a11y.json", "criteria": { - "1.4.1": { "group": "Manual" }, "1.3.2": { "responsibility": "Shared", "flagged": true }, "2.4.6": { "group": "Manual", "flagged": true }, "3.3.2": { "flagged": true } }, "notApplicable": [ "1.1.1", + "1.4.1", "1.4.3", + "1.4.4", "1.4.5", "1.4.11", + "1.4.12", "2.1.1", "2.1.2", "2.4.7", @@ -20,6 +22,7 @@ "2.5.8", "3.2.1", "3.2.2", - "3.2.4" + "3.2.4", + "4.1.2" ] } diff --git a/packages/mui-material/src/FormGroup/accessibility.md b/packages/mui-material/src/FormGroup/accessibility.md index 899492a1d32685..2efb2bf5bf8ea5 100644 --- a/packages/mui-material/src/FormGroup/accessibility.md +++ b/packages/mui-material/src/FormGroup/accessibility.md @@ -8,15 +8,17 @@ Rated against WCAG 2.2 Level A and AA. See the [reports legend](../accessibility FormGroup arranges a set of checkboxes or switches. It renders a `div` with no role. The group semantics come from a `fieldset` with a legend, or from `role="group"` with a name. For radio buttons, use RadioGroup. +FormGroup rates layout criteria (1.3.2, 1.4.10, 2.4.3) because it sets the layout, and group criteria (1.3.1, 2.4.6, 3.3.2) because `role="group"` makes it the named group element. + | Result | Count | | :-------------------- | :---- | -| ✅ Supports | 11 | +| ✅ Supports | 7 | | ⚠️ Partially Supports | 0 | | ❌ Does Not Support | 0 | -| ➖ Not Applicable | 44 | -| 🚩 Flagged | 4/11 | +| ➖ Not Applicable | 48 | +| 🚩 Flagged | 3/7 | ## Known gaps @@ -61,25 +63,6 @@ None. **Pass:** each instruction names the group by its label. -#### 1.4.1 Use of Color · A - -`🚩` · `✅ Supports` · `◐ Shared` - - - -- The built-in error cues are color only: the legend and the helper text turn red. FormGroup gets the `error` class but no visual style of its own. -- The message text must name the error, so that it is clear without color. The author writes it. In the CheckboxesGroup docs demo, the helper text says "You can display an error" in both states, so only the color changes. - -**Manual testing steps** - -1. Put a group in the error state. -2. In Chrome DevTools, open the Rendering tab, and select Emulate vision deficiencies > Achromatopsia. -3. Read the message text. - - - -**Pass:** the message text names the error without color, for example "Error: select at least one delivery day." - #### 2.4.6 Headings and Labels · AA `🚩` · `✅ Supports` · `◐ Shared` @@ -107,11 +90,14 @@ Tested by: unit `FormGroup.test.js` -- FormGroup renders a `div` with no role, so by itself it does not tell assistive technology that the controls form a group. A unit test confirms this. -- Inside `FormControl component="fieldset"` with `FormLabel component="legend"`, the `fieldset` is the group, and the legend is its name. With `role="group"` and `aria-labelledby` on FormGroup, FormGroup itself is a named group. Unit tests confirm both patterns. +- FormGroup renders a `div` with no role, so by itself it does not tell assistive technology that the controls form a group. +- With `role="group"` and `aria-labelledby` on FormGroup, FormGroup itself is a named group. A unit test confirms that FormGroup passes these attributes to its root element. +- No docs page or demo documents `role="group"` on FormGroup. The docs show only the `fieldset` pattern, in CheckboxesGroup and SwitchesGroup. A follow-up must document the `role="group"` pattern. +- Inside `FormControl component="fieldset"` with `FormLabel component="legend"`, the `fieldset` is the group, and the legend is its name. FormGroup adds nothing to this pattern. - A group without a `fieldset` or a role has no name. This is the author's responsibility. - FormControl does not connect a FormHelperText to the group. The author adds `aria-describedby`. In the error state, the checkboxes get no `aria-invalid`, because Checkbox and Switch take only `disabled` from FormControl. -- ARIA does not allow `aria-label` on an element with no role, and assistive technology can ignore it. axe-core `aria-prohibited-attr` reports it for review. The FormControlLabelPosition docs demos use this pattern. +- ARIA does not allow `aria-label` on an element with no role, and assistive technology can ignore it. The FormControlLabelPosition docs demos use this pattern. axe-core `aria-prohibited-attr` reports it only as `incomplete`, and no fixture exercises it. +- In the ScrollPlayground Popper demo, each FormGroup starts with a Typography heading (`h6`), for example "Prevent Overflow". No `fieldset` or `aria-labelledby` connects the heading to the group, so the group has no name. This is a demo issue. **Manual testing steps** @@ -122,7 +108,7 @@ Tested by: unit `FormGroup.test.js` -**Pass:** the screen reader announces the group name once when focus enters the group. Without a `fieldset` or a role, the group has no name, and the author must add one. +**Pass:** the screen reader announces the group name once when focus enters the group. #### 3.3.2 Labels or Instructions · A @@ -133,8 +119,12 @@ Tested by: axe-core `form-field-multiple-labels` ✅ - The legend, or the element that `aria-labelledby` names, is the visible label of the group. FormGroup adds no label of its own. -- The legend asterisk of a required group is visual only. The group does not expose a required state, and a `fieldset` cannot carry `aria-required`. Write the rule in text, for example "Select at least one". -- Result from the checkbox, switch, app bar, and list demos: CheckboxesGroup, SwitchesGroup, and both FormControlLabelPosition demos have a group label. CheckboxLabels, SwitchLabels, CustomizedSwitches, MenuAppBar, and InteractiveList have none, but their controls are separate options, and each control has its own label. +- The required state of a group is a FormLabel gap. The [FormLabel report](../FormLabel/accessibility.md) rates it under 1.3.1. +- Result from the checkbox, switch, app bar, list, focus-visible, Popper, and CRUD dashboard demos: + - CheckboxesGroup, SwitchesGroup, both FormControlLabelPosition demos, and FocusVisibleCustomIcons have a group label. + - CheckboxLabels, SwitchLabels, CustomizedSwitches, MenuAppBar, InteractiveList, and FullFocusVisibleDemo have no group label. Their controls are separate options, and each control has its own label. In FullFocusVisibleDemo, the "Checkbox" text next to the group is a row caption of the showcase, not a group label. + - EmployeeForm, in the CRUD dashboard template, uses FormGroup as a layout wrapper for the whole form. Each field has its own label. + - ScrollPlayground shows a visible heading above each group. The heading is not connected to the group. See 1.3.1. **Manual testing steps** @@ -145,40 +135,8 @@ Tested by: axe-core `form-field-multiple-labels` ✅ **Pass:** each group that asks one question has a visible label and clear instructions. -#### 4.1.2 Name, Role, Value · A - -`✅ Supports` · `◐ Shared` - -Tested by: axe-core `aria-allowed-attr` ✅, `aria-conditional-attr` ✅, `aria-deprecated-role` ✅, `aria-hidden-focus` ✅, `aria-prohibited-attr` ✅, `aria-required-attr` ✅, `aria-roles` ✅, `aria-valid-attr` ✅, `aria-valid-attr-value` ✅, `duplicate-id-aria` ✅, `label` ✅, `nested-interactive` ✅ - - - -- FormGroup has no role, name, or value of its own. The controls inside it expose theirs. -- When used as documented, the container exposes the group role and a name: the `fieldset`, or `role="group"` on FormGroup. axe-core ARIA rules pass on the fixture. - -**Manual testing steps** - -1. With a screen reader, press Tab into a named group. -2. Listen to the group role and name, and the name and state of the first control. - - - -**Pass:** the screen reader announces the group and its name, then the control with its name and state. - ### ⚙️ Automated -#### 1.4.4 Resize Text · AA - -`✅ Supports` · `◐ Shared` - -Tested by: Playwright - - - -- The controls and their labels scale with the text size, and the group wraps them. A Playwright test finds no horizontal scroll or clipping in the states fixture at a 200% text size. - - - #### 1.4.10 Reflow · AA `✅ Supports` · `◐ Shared` @@ -191,18 +149,6 @@ Tested by: Playwright -#### 1.4.12 Text Spacing · AA - -`✅ Supports` · `◐ Shared` - -Tested by: Playwright - - - -- The group has no fixed height. A Playwright test applies the WCAG text-spacing values to the states fixture and finds no clipped text. - - - #### 2.4.3 Focus Order · A `✅ Supports` · `◐ Shared` @@ -211,7 +157,7 @@ Tested by: unit `FormGroup.test.js` -- The controls are tab stops in DOM order, also with `row`. A unit test confirms this. A second test, in a real browser, confirms that a row group shows its controls from left to right in tab order. +- The controls are tab stops in DOM order, also with `row`. A test in a real browser confirms that a row group shows its controls from left to right in tab order. @@ -221,8 +167,9 @@ Tested by: unit `FormGroup.test.js` - **1.2.1 Audio-only and Video-only (Prerecorded) (A), 1.2.2 Captions (Prerecorded) (A), 1.2.3 Audio Description or Media Alternative (Prerecorded) (A), 1.2.4 Captions (Live) (AA), 1.2.5 Audio Description (Prerecorded) (AA).** No audio or video. - **1.3.4 Orientation (AA).** Sets no orientation lock; a layout concern. - **1.3.5 Identify Input Purpose (AA).** Collects no information about the user through `autocomplete`. +- **1.4.1 Use of Color (A).** FormGroup renders no color of its own. It gets the `error` class in the error state, but no error style. The legend and the helper text show the error color. - **1.4.2 Audio Control (A).** Emits no audio. -- **1.4.3 Contrast (Minimum) (AA), 1.4.5 Images of Text (AA).** FormGroup renders no text of its own. The legend and the labels own these criteria. +- **1.4.3 Contrast (Minimum) (AA), 1.4.4 Resize Text (AA), 1.4.5 Images of Text (AA), 1.4.12 Text Spacing (AA).** FormGroup renders no text of its own. The legend and the labels own these criteria. - **1.4.11 Non-text Contrast (AA).** FormGroup renders no graphical object and no component boundary. - **1.4.13 Content on Hover or Focus (AA).** Shows no additional content on hover or focus; a `Tooltip` wrapper would own this. - **2.1.1 Keyboard (A), 2.1.2 No Keyboard Trap (A), 2.4.7 Focus Visible (AA), 2.4.11 Focus Not Obscured (Minimum) (AA), 2.5.2 Pointer Cancellation (A), 2.5.3 Label in Name (A), 2.5.8 Target Size (Minimum) (AA), 3.2.1 On Focus (A), 3.2.2 On Input (A), 3.2.4 Consistent Identification (AA).** FormGroup is not focusable and has no action of its own. The controls inside it cover these. @@ -240,6 +187,7 @@ Tested by: unit `FormGroup.test.js` - **3.3.1 Error Identification (A), 3.3.3 Error Suggestion (AA), 3.3.4 Error Prevention (Legal, Financial, Data) (AA).** Collects and validates no input; these belong to the form or process. - **3.3.7 Redundant Entry (A).** Captures no previously entered data to repopulate. - **3.3.8 Accessible Authentication (Minimum) (AA).** No authentication step; the duty falls on the credential fields. +- **4.1.2 Name, Role, Value (A).** FormGroup sets no role, and its own markup supplies no name. The group role and name pattern is rated under 1.3.1. - **4.1.3 Status Messages (AA).** Emits no status messages; state changes are covered by 4.1.2. ## Level AAA diff --git a/packages/mui-material/src/accessibility.md b/packages/mui-material/src/accessibility.md index 99a98b9177ce7e..a2eb4458fc41d3 100644 --- a/packages/mui-material/src/accessibility.md +++ b/packages/mui-material/src/accessibility.md @@ -189,7 +189,7 @@ Edit the JSON files, not this table. | [Button](./Button/accessibility.md) | 24 | 3 | 0 | 28 | — | 7/27 | | [Checkbox](./Checkbox/accessibility.md) | 24 | 1 | 0 | 30 | — | 3/25 | | [CircularProgress](./CircularProgress/accessibility.md) | 8 | 3 | 0 | 44 | — | 5/11 | -| [FormGroup](./FormGroup/accessibility.md) | 11 | 0 | 0 | 44 | — | 4/11 | +| [FormGroup](./FormGroup/accessibility.md) | 7 | 0 | 0 | 48 | — | 3/7 | | [LinearProgress](./LinearProgress/accessibility.md) | 8 | 3 | 0 | 44 | — | 5/11 | | [Radio](./Radio/accessibility.md) | 24 | 1 | 0 | 30 | — | 2/25 | | [RadioGroup](./RadioGroup/accessibility.md) | 7 | 0 | 0 | 30 | 18 | 3/7 | diff --git a/packages/mui-material/src/manual-testing.md b/packages/mui-material/src/manual-testing.md index badc17dbd2ca1a..b6ef68b70f20e4 100644 --- a/packages/mui-material/src/manual-testing.md +++ b/packages/mui-material/src/manual-testing.md @@ -108,14 +108,12 @@ Run the rows matching what you touched, for the component you touched. ## FormGroup -6 checks · full procedures in [`FormGroup/accessibility.md`](./FormGroup/accessibility.md) +4 checks · full procedures in [`FormGroup/accessibility.md`](./FormGroup/accessibility.md) -- [ ] 🔁 **1.3.1 Info and Relationships** — the screen reader announces the group name once when focus enters the group. Without a `fieldset` or a role, the group has no name, and the author must add one. +- [ ] 🔁 **1.3.1 Info and Relationships** — the screen reader announces the group name once when focus enters the group. - [ ] 🔍 **1.3.2 Meaningful Sequence** — the reading order matches the visual order. -- [ ] 🔍 **1.4.1 Use of Color** — the message text names the error without color, for example "Error: select at least one delivery day." - [ ] 🔍 **2.4.6 Headings and Labels** — each group name tells the purpose of the set, for example "Toppings". - [ ] 🔁 **3.3.2 Labels or Instructions** — each group that asks one question has a visible label and clear instructions. -- [ ] 🔁 **4.1.2 Name, Role, Value** — the screen reader announces the group and its name, then the control with its name and state. ## LinearProgress diff --git a/test/regressions/a11y/criteriaSuites.mjs b/test/regressions/a11y/criteriaSuites.mjs index a562dc8d402bfe..472ac3b1495ca0 100644 --- a/test/regressions/a11y/criteriaSuites.mjs +++ b/test/regressions/a11y/criteriaSuites.mjs @@ -32,6 +32,8 @@ export const CSS_LAYOUT_SUITES = [ // FormGroup has no docs page of its own, so the layout suite runs on its a11y fixture. component: 'FormGroup', route: '/a11y-form-group/FormGroupA11yStates', + // FormGroup renders no text, so the report rates 1.4.4 and 1.4.12 not applicable. + skipCriteria: ['1.4.4', '1.4.12'], }, { component: 'LinearProgress',