Skip to content

[test][button] Add axe test coverage and WCAG conformance report - #48916

Open
michelengelen wants to merge 3 commits into
mui:masterfrom
michelengelen:a11y/stack/button
Open

[test][button] Add axe test coverage and WCAG conformance report#48916
michelengelen wants to merge 3 commits into
mui:masterfrom
michelengelen:a11y/stack/button

Conversation

@michelengelen

@michelengelen michelengelen commented Aug 4, 2026

Copy link
Copy Markdown
Member

Expands axe coverage for Button and adds its WCAG 2.2 Level A/AA conformance report, plus the shared reports legend at packages/mui-material/src/accessibility.md.

Result Count
✅ Supports 23
⚠️ Partially Supports 4
❌ Does Not Support 0
➖ Not Applicable 28

Known gaps: contrast on info/warning contained buttons (1.4.3), focus-indicator contrast and its removal via disableRipple (1.4.11, 2.4.7), and no live region for the loading state (4.1.3).

Report: Button/accessibility.md

Important

How to review this PR

Layer 3 of 14 in a stacked series (#48915#48926). Its branch is built on a11y/stack/css-layout (#48936). GitHub cannot chain PR bases across a fork, so every PR in the series targets master instead.

Review only the last commit5c23780. Everything above it belongs to the layers below, and disappears as those merge.

Original work by @mj12albert. Supersedes #48708, rebased onto current master.

Part of the WCAG conformance effort (#14187).

@code-infra-dashboard

code-infra-dashboard Bot commented Aug 4, 2026

Copy link
Copy Markdown

Deploy preview

Bundle size

Bundle Parsed size Gzip size
@mui/material 0B(0.00%) 0B(0.00%)
@mui/lab 0B(0.00%) 0B(0.00%)
@mui/private-theming 0B(0.00%) 0B(0.00%)
@mui/system 0B(0.00%) 0B(0.00%)
@mui/utils 0B(0.00%) 0B(0.00%)

Details of bundle changes


Check out the code infra dashboard for more information about this PR.

@michelengelen michelengelen added test accessibility a11y scope: button Changes related to the button. labels Aug 5, 2026
@michelengelen
michelengelen force-pushed the a11y/stack/button branch 5 times, most recently from 212555a to 9b9147b Compare August 6, 2026 10:43
@michelengelen
michelengelen marked this pull request as ready for review August 7, 2026 10:15
@michelengelen
michelengelen force-pushed the a11y/stack/button branch 2 times, most recently from 764f1ed to d60b37a Compare August 7, 2026 13:00
Lets a fixture assert every axe rule it exercises rather than only the
CSS-dependent visual ones, which is what the per-component WCAG reports
need.
Adds a table-driven Playwright suite covering 1.4.10 Reflow, 1.4.4 Resize
Text and 1.4.12 Text Spacing across the assessed components. axe has no rule
for any of them, so they were rated Manual on evidence from source review.
@michelengelen

Copy link
Copy Markdown
Member Author

PR review

No findings.

This PR sets the template for the stack and it is internally consistent. Checked: the demoMeta enrolment matches the 15 keys in buttons.a11y.json; the only recorded non-pass is the deliberate color-contrast fail on ButtonA11yColorMatrix, whose rule skips exactly that assertion; the screenshot opt-out/opt-in pair for the ButtonA11y* fixtures is ordered correctly for last-match-wins; nativeButton exists on ButtonBase, so the ButtonA11yNonNative demo is valid; the count table (23 + 4 + 28 = 55 criteria, 8/27 flagged) matches the WCAG 2.2 A/AA total and the later rollup.

Residual risk: the 371-line conformance report was spot-checked against the code, not verified claim by claim, and the new demos are regression-harness-only (not referenced from buttons.md), which is deliberate per the in-code comments.

Verdict

Approve - the enrolment, fixtures, tests, and report counts are consistent end to end.


🤖 Review generated with Claude Code

@github-actions github-actions Bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Aug 19, 2026

@JCQuintas JCQuintas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the last commit only (5c23780), against master with #48915 and #48936 already in.

Looks good overall: the counts are self-consistent (23 + 4 + 0 + 28 = 55, which is exactly WCAG 2.2 A/AA once 4.1.1 is dropped, and the 8/27 flag count matches the body), the harness wiring is right (ButtonA11y* opted out of screenshots with ButtonA11yTextSpacing re-enabled by last-match-wins), the non-native tabIndex=-1 claim checks out against useButtonBase.ts, and all doc links resolve. Comments below are mostly wording accuracy on the report, since this text ends up published in #48926.

The one I would not merge without: the 1.4.3 known gap names only contained, but the same two palette colors fail as text and outlined labels too.

Two housekeeping notes:

  • The branch is CONFLICTING with master. Button.test.js moved to import { describe, expect, it, vi } from 'vitest' upstream, so the added import { expect } from 'chai' hunk collides on rebase. sinon's spy is still fine, 57 files in packages/mui-material/src still use it.
  • Argos shows 1 added screenshot, which is ButtonA11yTextSpacing. Expected, that is the demo the rule deliberately keeps enabled.

Non-blocking observation: ButtonA11yTextSpacing overlaps what #48936 already automates, since registerCssLayoutSuites injects TEXT_SPACING_CSS on /docs-components-buttons/BasicButtons. The inline comment already says it adds no unique axe coverage, so the value here is purely the permanent Argos snapshot. Fine if that is intended, just worth being explicit that it is a screenshot we now maintain forever.


## Known gaps

- ⚠️ **1.4.3 Contrast (Minimum).** `info` and `warning` contained buttons fall short of 4.5:1.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This under-scopes the gap. text and outlined fail on the same two colors, because their label is palette.<color>.main on the surface behind it:

  • info.main #0288d1 on white: 3.86:1
  • warning.main #ed6c02 on white: 3.11:1

Those are the same ratios as the contained case (white on main), so the failure is not variant-specific. ButtonA11yColorMatrix renders all 21 combinations, so axe flagged them there too.

Suggested wording: "info and warning fall short of 4.5:1 in every variant".


`⚠️ Partially Supports` · `● Component`

- `info` and `warning` contained buttons do not meet `4.5:1`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same scope point as the Known gaps entry, and one traceability point on top of it: buttons.a11y.json records only the rule status (color-contrast: fail), no nodes or ratios, so a reader has no way to check which combinations failed or by how much.

Since this is the criterion the report is most likely to be challenged on, consider listing the failing pairs with their ratios in this section, or saying explicitly that the per-node detail lives in the test output rather than in the JSON.

## Scope and test environment

- **Standard.** WCAG 2.2, Level A and AA.
- **Component version.** `@mui/material` 9.1.1.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale: packages/mui-material/package.json on master is 9.3.1.

Pinning a patch version means every report in the stack goes stale on the next release. Consider 9.x, or bumping right before merge.


- Toggling the button's setting (`loading` to disabled, or an `aria-pressed` toggle) changes no context on its own.
- Whether an author's handler couples that change to navigation or a new window without warning is an author decision.
- Confirmed by a unit test in [`./Button.test.js`](./Button.test.js) (toggling `aria-pressed` or `loading` does not activate the button).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test does not toggle anything. It renders one static aria-pressed button and one static loading button, asserts callCount === 0 before any interaction, then clicks the first one.

So the evidence is "rendering these states does not fire onClick", not "toggling them does not". Either soften the wording here, or rerender with changed props in the test so the claim is backed.

`✅ Supports` · `● Component`

- Activation runs on `click`, fired on pointer-up over the target. `onMouseDown` only starts the ripple, and releasing off the target cancels, so nothing runs on the down event.
- Confirmed by a unit test in [`./Button.test.js`](./Button.test.js) (`mousedown` does not activate; `click` does). Covered by unit tests.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing "Covered by unit tests." is redundant right after the explicit "Confirmed by a unit test in ..." sentence. Same leftover on line 312 under 2.5.3.

| RadioGroup | | | | |
| Switch | | | | |
| ToggleButton | | | | |
| ToggleButtonGroup | | | | |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The table pre-lists 9 components, but the program covers 12: Accordion, AccordionSummary and TextField have no row. registerCssLayoutSuites on master already names all of them.

Either add the three missing rows now, or drop the empty rows entirely and have each component PR append its own. Empty rows for components that do not have a report yet read as gaps rather than as work in progress.

test: 'docs/data/material/components/buttons/ButtonA11yColorMatrix',
enabled: true,
assertions: 'all',
skipAssertions: ['color-contrast'],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only skipAssertions in the repo, and it is the one rule here without a trailing reason comment, while every neighbouring rule carries one ("Redundant", "Needs interaction", "A11y-only coverage fixtures").

Suggest something like:

skipAssertions: ['color-contrast'], // Known info/warning palette failures: recorded in the JSON, not asserted

@@ -0,0 +1,9 @@
{variants.map((variant) => (

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing consumes this. buttons.md does not reference any of the ButtonA11y* demos, so the .preview never renders. Same for ButtonA11yNonNative.tsx.preview, and ButtonA11ySemanticStates / ButtonA11yTextSpacing ship without one, so the set is inconsistent either way. Suggest dropping both .preview files.

expect(handleClick.callCount).to.equal(0);
});

it('3.2.2 On Input: state changes only from explicit activation, never on its own', async () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name says "state changes only from explicit activation", but nothing changes state during the test: both buttons are rendered with their state fixed, and the first assertion only proves that rendering did not fire onClick.

If the intent is the 3.2.2 claim in the report, the test needs a rerender flipping aria-pressed (and loading) and an assertion that no handler ran across that change. Otherwise the report sentence should be scoped down to what this asserts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

accessibility a11y PR: out-of-date The pull request has merge conflicts and can't be merged. scope: button Changes related to the button. test

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants