Skip to content

[Stepper][MenuList][Tabs] Improve accessibility#47687

Merged
silviuaavram merged 76 commits into
mui:masterfrom
silviuaavram:feat/stepper-accessibility
Mar 12, 2026
Merged

[Stepper][MenuList][Tabs] Improve accessibility#47687
silviuaavram merged 76 commits into
mui:masterfrom
silviuaavram:feat/stepper-accessibility

Conversation

@silviuaavram

@silviuaavram silviuaavram commented Jan 27, 2026

Copy link
Copy Markdown
Member

Fixes #43689.
Fixes #32826.

Stepper:

  • improve markup for stepper and step, from divs to ol and li.
  • implement roving tabindex focus for the step buttons.
  • step buttons also receive the role of tab, aria-posinset, aria-setsize and aria-selected instead of aria-current.
  • stepper that has step buttons will have the role of tablist and aria-orientation
  • step that has step buttons will have the role of presentation.

Technical:

  • roving tabindex has a separate hook that keeps track of the current focusable item (by index).
  • it exports 2 prop getters, one for container, one for item, and a focusNext function
  • one source of truth for tabindex values -> the hook.
  • update the Tabs and MenuList to also use the hook instead of their own logic
  • exported focusNext from the hook, for MenuList to use it for character key focus, in order to keep the single source of truth for the tabindex.
  • using refs instead of parsing DOM directly for getting the focusable siblings

UX for roving tabindex:

  • tabindex now follows focus. before, it stayed on the selected item, even as you moved away with arrow keys. this created issues:
    • if you tabbed from and back, the previously focused item was forgotten, you would've ended up on the selected item
    • if you moved selection from selected item with arrows, then tabbed / shift tabbed, you could have ended up with the selected item focused, which should not have happened
  • this applies to Stepper, Menu / MenuList, Tabs.

Copilot AI review requested due to automatic review settings January 27, 2026 08:27
@mui-bot

mui-bot commented Jan 27, 2026

Copy link
Copy Markdown

Netlify deploy preview

Bundle size report

Bundle Parsed size Gzip size
@mui/material 🔺+1.61KB(+0.31%) 🔺+678B(+0.45%)
@mui/lab 0B(0.00%) 0B(0.00%)
@mui/system 0B(0.00%) 0B(0.00%)
@mui/utils 🔺+1.94KB(+15.10%) 🔺+761B(+15.11%)

Details of bundle changes

Generated by 🚫 dangerJS against 43d0bf6

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This pull request improves the accessibility of the Stepper component by implementing semantic HTML markup and keyboard navigation following ARIA best practices.

Changes:

  • Replaced div elements with semantic ol/li elements for Stepper and Step components
  • Implemented roving tabindex focus management with arrow key navigation for step buttons
  • Added ARIA attributes (aria-posinset, aria-setsize, aria-orientation) to improve screen reader support
  • Refactored StepperContext to export only useStepperContext hook and StepperContextProvider, removing the default export

Reviewed changes

Copilot reviewed 13 out of 15 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
packages/mui-material/src/Stepper/utils/useRovingTabIndexFocus.ts New hook implementing circular keyboard navigation with arrow keys, skipping disabled steps
packages/mui-material/src/Stepper/utils/useRovingTabIndexFocus.test.tsx Comprehensive unit tests for the roving tabindex hook
packages/mui-material/src/Stepper/index.js Removed default export of StepperContext, keeping named exports
packages/mui-material/src/Stepper/index.d.ts Updated TypeScript exports to match JavaScript changes
packages/mui-material/src/Stepper/StepperContext.ts Added new context properties for focus management, marked as @internal, exported StepperContextProvider
packages/mui-material/src/Stepper/Stepper.test.tsx Updated test to expect HTMLOListElement instead of HTMLDivElement
packages/mui-material/src/Stepper/Stepper.js Changed root element from div to ol, integrated roving tabindex, added aria-orientation
packages/mui-material/src/StepLabel/StepLabel.js Updated to use useStepperContext hook instead of direct context import
packages/mui-material/src/StepContent/StepContent.js Updated to use useStepperContext hook instead of direct context import
packages/mui-material/src/StepConnector/StepConnector.js Updated to use useStepperContext hook instead of direct context import
packages/mui-material/src/StepButton/StepButton.test.js Added StepperContextProvider wrapper to all test cases
packages/mui-material/src/StepButton/StepButton.js Integrated roving tabindex, added ARIA attributes, implemented keyboard and click handlers
packages/mui-material/src/StepButton/StepButton.d.ts Added TypeScript definitions for onClick and onKeyDown props
packages/mui-material/src/Step/Step.test.js Updated tests for li element and added StepperContextProvider wrappers
packages/mui-material/src/Step/Step.js Changed root element from div to li, updated to use useStepperContext hook

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/mui-material/src/Stepper/utils/useRovingTabIndexFocus.test.tsx Outdated
Comment thread packages/mui-material/src/Stepper/utils/useRovingTabIndexFocus.ts Outdated
Comment thread packages/mui-material/src/Stepper/Stepper.js
Comment thread packages/mui-material/src/Stepper/StepperContext.ts Outdated
Comment thread packages/mui-material/src/Stepper/Stepper.js Outdated
Comment thread packages/mui-material/src/Stepper/StepperContext.ts Outdated
Comment thread packages/mui-material/src/StepButton/StepButton.js Outdated
Comment thread packages/mui-material/src/StepButton/StepButton.js Outdated
Comment thread packages/mui-material/src/Stepper/Stepper.js
Comment thread packages/mui-material/src/StepButton/StepButton.js Outdated
@silviuaavram silviuaavram marked this pull request as draft January 27, 2026 08:41
@silviuaavram silviuaavram force-pushed the feat/stepper-accessibility branch from 2efcaec to a84bfad Compare January 27, 2026 17:44
@silviuaavram silviuaavram marked this pull request as ready for review January 27, 2026 17:56
@zannager zannager added the scope: stepper Changes related to the stepper. label Jan 29, 2026
@zannager zannager requested a review from mj12albert January 29, 2026 16:15

@ZeeshanTamboli ZeeshanTamboli 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.

@silviuaavram Tested it initially with NVDA screen reader. Nice improvements.

  1. With NVDA enabled, the Left/Right arrow keys don't work on the non-linear stepper:
    https://deploy-preview-47687--material-ui.netlify.app/material-ui/react-stepper/#non-linear and nothing is announced as well. When NVDA is off, arrow-key navigation works as expected.
  2. Is it expected that Tab key should move focus to the next step? Since this isn't a tablist, I'm not sure. On the docs site (https://mui.com/material-ui/react-stepper/#non-linear), Tab moves between steps, but in this PR it jumps directly to the "Next" button.
  3. If useStepperContext is intended to be a public API, I think we should document how to use it with a custom stepper demo.

@ZeeshanTamboli ZeeshanTamboli added accessibility a11y type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature. labels Jan 31, 2026
@silviuaavram

silviuaavram commented Feb 2, 2026

Copy link
Copy Markdown
Member Author

Hey @ZeeshanTamboli thanks for your input!

  1. I don't know what to say, especially that I don't have a windows setup at the moment. For example, does this stepper work with NVDA?
  2. I expect Tab to only work on one step at a time. Focusing other steppers with keyboard should work with arrows instead.
  3. useStepperContext should not be public, as StepperContext wasn't public either, right? The hook is meant be used internally, instead of doing useContext(StepperContext).

@ZeeshanTamboli

Copy link
Copy Markdown
Member

Hey @ZeeshanTamboli thanks for your input!

  1. I don't know what to say, especially that I don't have a windows setup at the moment. For example, does this stepper work with NVDA?

Yes, it works. I am able to navigate with left/right arrow keys with NVDA enabled in this demo but not in this PR's demo.

  1. I expect Tab to only work on one step at a time. Focusing other steppers with keyboard should work with arrows instead.

Ok, so you mean, pressing Tab should focus only on the last focused step in the Stepper and not move to the next step like in production?

  1. useStepperContext should not be public, as StepperContext wasn't public either, right? The hook is meant be used internally, instead of doing useContext(StepperContext).

I read this only export useStepperContext in order to control the usage better. in the PR description, so I thought it is exported.

Comment thread packages/mui-material/src/Stepper/utils/useRovingTabIndexFocus.ts Outdated
@silviuaavram silviuaavram force-pushed the feat/stepper-accessibility branch from d598657 to be10951 Compare February 10, 2026 13:38
@silviuaavram silviuaavram marked this pull request as draft February 18, 2026 09:10
@github-actions github-actions Bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Feb 19, 2026
@silviuaavram silviuaavram force-pushed the feat/stepper-accessibility branch from 1f0ac0d to 353af74 Compare February 19, 2026 14:50
@github-actions github-actions Bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Feb 19, 2026
@silviuaavram silviuaavram force-pushed the feat/stepper-accessibility branch 5 times, most recently from 2c39c92 to 179c988 Compare February 26, 2026 09:50
@github-actions github-actions Bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Feb 26, 2026
@silviuaavram silviuaavram marked this pull request as ready for review February 26, 2026 11:54
@silviuaavram silviuaavram force-pushed the feat/stepper-accessibility branch from c95b272 to 917952a Compare February 26, 2026 11:56
@silviuaavram silviuaavram force-pushed the feat/stepper-accessibility branch from ef56fe4 to 43d0bf6 Compare March 12, 2026 08:02
@silviuaavram silviuaavram merged commit da5b514 into mui:master Mar 12, 2026
22 checks passed
@silviuaavram silviuaavram deleted the feat/stepper-accessibility branch March 12, 2026 08:42
@ArmandRedgate

ArmandRedgate commented May 1, 2026

Copy link
Copy Markdown

Hey,

This probably isn't the best place to say this, but this work has caused some failures in our accessibility scanning because the StepConnectors are div children to the ol stepper, and those are not allowed.

image

One way to fix this is to set aria-hidden="true" on those StepConnectors. Any chance you could do that or something like it? Consumers of MUI cannot do this I think because of no slotProps for the connector, at least none that I could find.

Thanks.

@mj12albert

Copy link
Copy Markdown
Member

@ArmandRedgate Would you mind opening a new issue 🙏

@ArmandRedgate

ArmandRedgate commented May 1, 2026

Copy link
Copy Markdown

@mj12albert you know what?

If that's alright I'll try making this fix my first open source contribution. Embarrassing given I've been coding professionally for over 5 years. I'll make a PR.

@oliviertassinari

Copy link
Copy Markdown
Member

It's more of an issue with having a correct HTML structure, than a11y; no?

SCR-20260501-rzrw

https://validator.w3.org/nu/?doc=https%3A%2F%2Fdeploy-preview-48397--material-ui.netlify.app%2Fmaterial-ui%2Freact-stepper%2F

@ArmandRedgate

Copy link
Copy Markdown

It's more of an issue with having a correct HTML structure, than a11y; no?

Yes, but the only alternative I can think of to hiding them is to make them list items, and you should not do that as they are not part of the list. They are for visual display purposes only.

@ArmandRedgate

Copy link
Copy Markdown

I forgot to link the completed PR, here you go: #48397

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

Labels

accessibility a11y breaking change Introduces changes that are not backward compatible. scope: stepper Changes related to the stepper. type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Stepper] Lacks accessibility Accessibility | Narrator is not announcing the Stepper headings as "selected"

10 participants