Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"FormControlLabelA11yStates": {
"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"]
},
"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"]
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<p class="description">How Material UI components conform to WCAG 2.2 Level A and AA, reported in VPAT terms for procurement and accessibility review.</p>

:::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 the 14 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.
:::

## About this report
Expand Down Expand Up @@ -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 |
| [FormControlLabel](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/FormControlLabel/accessibility.md) | 7 | 7 | 14 | 14 | 0 | 13/14 | 5 |
| [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 |
Expand Down Expand Up @@ -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.** 14 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.
- **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.
Expand Down
152 changes: 151 additions & 1 deletion docs/data/material/getting-started/accessibility/scorecard.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"standard": "WCAG 2.2 Level A and AA",
"componentCount": 13,
"componentCount": 14,
"totals": {
"rated": 32,
"supports": 27,
Expand Down Expand Up @@ -1538,6 +1538,156 @@
"automated": 1
}
},
{
"component": "FormControlLabel",
"counts": {
"supports": 14,
"partiallySupports": 0,
"doesNotSupport": 0,
"notApplicable": 41,
"flagged": 1,
"rated": 14
},
"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": 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": "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.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": "2.5.8",
"name": "Target Size (Minimum)",
"level": "AA",
"conformance": "Supports",
"responsibility": "Shared",
"group": "Automated",
"flagged": false
},
{
"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
}
],
"summary": {
"rated": 14,
"levelA": 7,
"levelAA": 7,
"supports": 14,
"partiallySupports": 0,
"doesNotSupport": 0,
"verified": 13,
"automated": 5
}
},
{
"component": "LinearProgress",
"counts": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import FormControlLabel, {
} from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
import FormControl from '@mui/material/FormControl';
import Radio from '@mui/material/Radio';
import Switch from '@mui/material/Switch';
import Typography from '@mui/material/Typography';
import describeConformance from '../../test/describeConformance';

Expand Down Expand Up @@ -343,4 +345,97 @@ describe('<FormControlLabel />', () => {

expect(screen.getByTestId('control')).to.have.attribute('value', 'test');
});

// Deterministic checks for the WCAG success criteria the FormControlLabel
// report rates as the label's own. See ./accessibility.md.
describe('WCAG 2.2 conformance', () => {
it('1.3.1 Info and Relationships: associates the control with its label by wrapping it', () => {
const { container } = render(
<FormControlLabel control={<Checkbox />} label="Email notifications" />,
);
const label = container.firstChild;

expect(label).to.have.tagName('label');
expect(label).to.contain(screen.getByRole('checkbox'));
expect(label).to.contain(screen.getByText('Email notifications'));
});

it('2.5.3 Label in Name: a Checkbox exposes the visible label as its accessible name', () => {
render(<FormControlLabel control={<Checkbox />} label="Email notifications" />);
// getByRole with `name` only resolves if the accessible name matches the visible label.
expect(screen.getByRole('checkbox', { name: 'Email notifications' })).not.to.equal(null);
});

it('2.5.3 Label in Name: a Radio exposes the visible label as its accessible name', () => {
render(<FormControlLabel control={<Radio />} value="standard" label="Standard shipping" />);

expect(screen.getByRole('radio', { name: 'Standard shipping' })).not.to.equal(null);
});

it('2.5.3 Label in Name: a Switch exposes the visible label as its accessible name', () => {
render(<FormControlLabel control={<Switch />} label="Dark mode" />);

expect(screen.getByRole('switch', { name: 'Dark mode' })).not.to.equal(null);
});

it('2.5.3 Label in Name: the required asterisk is excluded from the accessible name', () => {
render(<FormControlLabel required control={<Checkbox />} label="Accept the terms" />);

// The name is the label text; the asterisk is decorative and not in the name.
expect(screen.getByRole('checkbox', { name: 'Accept the terms' })).not.to.equal(null);
});

describe('4.1.2 Name, Role, Value', () => {
it('exposes the control role with the label as its accessible name', () => {
render(<FormControlLabel control={<Checkbox />} label="Subscribe" />);

expect(screen.getByRole('checkbox', { name: 'Subscribe' })).to.have.property(
'checked',
false,
);
});

it('forwards required to the control without adding the asterisk to the name', () => {
const { container } = render(
<FormControlLabel required control={<Checkbox />} label="Accept the terms" />,
);
const checkbox = screen.getByRole('checkbox', { name: 'Accept the terms' });

expect(checkbox).to.have.property('required', true);
// The visible text carries the asterisk, but the control's name does not.
expect(container.querySelector('label')).to.have.text('Accept the terms\u2009*');
});
});

it('toggles the wrapped control when the label text is clicked', async () => {
const { user } = render(
<FormControlLabel control={<Checkbox />} label="Email notifications" />,
);
const checkbox = screen.getByRole('checkbox', { name: 'Email notifications' });
expect(checkbox).to.have.property('checked', false);

await user.click(screen.getByText('Email notifications'));
expect(checkbox).to.have.property('checked', true);
});

it('keeps the control before the label in the DOM for every labelPlacement', () => {
['end', 'start', 'top', 'bottom'].forEach((labelPlacement) => {
const { container, unmount } = render(
<FormControlLabel
control={<Checkbox />}
label="Email notifications"
labelPlacement={labelPlacement}
/>,
);
const input = container.querySelector('input');
const labelText = screen.getByText('Email notifications');

// DOM order is control then label regardless of the visual placement.
// The control and the label are in separate subtrees, so the position
// is exactly DOCUMENT_POSITION_FOLLOWING when the label comes after it.
expect(input.compareDocumentPosition(labelText)).to.equal(Node.DOCUMENT_POSITION_FOLLOWING);
unmount();
});
});
});
});
20 changes: 20 additions & 0 deletions packages/mui-material/src/FormControlLabel/accessibility.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"axe": "docs/data/material/components/form-control-label/form-control-label.a11y.json",
"criteria": {
"1.3.2": { "responsibility": "Shared" },
"2.5.8": { "responsibility": "Shared" }
},
"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",
"3.2.1",
"3.2.2",
"3.2.4"
]
}
Loading
Loading