Skip to content

[MenuItem] add aria-checked for checkbox and radio menu items#48651

Open
siriwatknp wants to merge 1 commit into
mui:masterfrom
siriwatknp:feat/menuitem-checkbox-role
Open

[MenuItem] add aria-checked for checkbox and radio menu items#48651
siriwatknp wants to merge 1 commit into
mui:masterfrom
siriwatknp:feat/menuitem-checkbox-role

Conversation

@siriwatknp

@siriwatknp siriwatknp commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

Makes MenuItem expose correct ARIA for checkable menus, following the WAI-ARIA menu pattern (items use menuitemcheckbox/menuitemradio + aria-checked).

When role is menuitemcheckbox or menuitemradio, the existing selected prop drives aria-checked. No new prop, no breaking changes.

Behavior

Markup Result
<MenuItem role="menuitemcheckbox" selected /> aria-checked="true"
<MenuItem role="menuitemcheckbox" /> aria-checked="false" (unchecked item)
<MenuItem role="menuitemradio" selected /> aria-checked="true"
<MenuItem selected /> (no role) unchanged — role="menuitem", presentational only, no ARIA state
Select options (role="option") unaffected — no aria-checked
  • For checkbox/radio roles, selected drives aria-checked (omitted selectedaria-checked="false"), and no aria-selected is emitted.
  • selected stays presentational for other roles, so existing menus and Select are untouched.

Docs

New Checkbox and radio menu items section in the Menus page with two demos:

  • CheckboxMenu — independent toggles (menuitemcheckbox).
  • RadioMenu — single choice in a group (menuitemradio), using radio-button icons.

Tests

  • selected drives aria-checked (true/false) for menuitemcheckbox and menuitemradio, with no aria-selected.
  • menuitemcheckbox with selected omitted → aria-checked="false".
  • non-checkable roles (e.g. option) keep selected presentational, no aria-checked.
  • plain <MenuItem selected /> keeps role="menuitem" with no ARIA state.

@siriwatknp siriwatknp added scope: menu Changes related to the menu. type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature. package: material-ui Specific to Material UI. labels Jun 10, 2026
@code-infra-dashboard

code-infra-dashboard Bot commented Jun 10, 2026

Copy link
Copy Markdown

Deploy preview

Bundle size

Bundle Parsed size Gzip size
@mui/material 🔺+83B(+0.02%) 🔺+55B(+0.04%)
@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.

@siriwatknp siriwatknp force-pushed the feat/menuitem-checkbox-role branch from 8c34236 to 0b9b296 Compare June 10, 2026 11:46
@siriwatknp siriwatknp changed the title [MenuItem] Add menuitemcheckbox role and aria-checked for selected items [MenuItem] Add checked prop with menuitemcheckbox role and aria-checked Jun 10, 2026
// A boolean `checked` turns the item into a checkable control, so expose the matching ARIA
// semantics. An explicit `role` is preserved (e.g. `menuitemradio`). Independent from `selected`.
const isCheckable = typeof checked === 'boolean';
const role = roleProp ?? (isCheckable ? 'menuitemcheckbox' : 'menuitem');

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.

Menu items should either be menuitemcheckboxs or menuitems, this logic dynamically changes an arbitrary menuitem in a group of them to a menuitemcheckbox, resulting in something like this which doesn't seem valid:

role="menu"
   role="menuitem"
   role="menuitem"
   role="menuitemcheckbox" // "checked"
   role="menuitem"

Reference: https://master--base-ui.netlify.app/react/components/menu#checkbox-items

@siriwatknp siriwatknp Jun 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I decided to change to a simpler approach, to add aria-checked only if user provide explicit role as menuitemcheckbox or menuitemradio.

@siriwatknp siriwatknp force-pushed the feat/menuitem-checkbox-role branch from 0b9b296 to 2627aaf Compare June 10, 2026 12:08
@siriwatknp siriwatknp changed the title [MenuItem] Add checked prop with menuitemcheckbox role and aria-checked [MenuItem] Support menuitemcheckbox/menuitemradio with aria-checked Jun 10, 2026
@siriwatknp siriwatknp force-pushed the feat/menuitem-checkbox-role branch from 2627aaf to fb6e545 Compare June 10, 2026 12:28
Comment on lines +184 to +188
// `menuitemcheckbox`/`menuitemradio` require `aria-checked`; derive it from `selected`
// (an omitted `selected` means unchecked). Other roles keep `selected` presentational.
const isCheckableRole = role === 'menuitemcheckbox' || role === 'menuitemradio';
const ariaChecked = isCheckableRole ? Boolean(props.selected) : undefined;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@mj12albert do you think this is a nice enhancement to auto added aria-checked?

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.

I think it's a necessity as nobody will bother doing it 😆

@siriwatknp siriwatknp force-pushed the feat/menuitem-checkbox-role branch from fb6e545 to 64bda4a Compare June 10, 2026 13:12
@mj12albert

Copy link
Copy Markdown
Member

@siriwatknp Did you consider adding a MenuCheckboxItem component? Setting role="menuitemcheckbox" doesn't feel like setting a deliberate prop, and menuitemcheckbox/radio don't look nice

ref,
disabled: props.disabled,
focusableWhenDisabled: itemsFocusableWhenDisabled,
selected: props.selected,

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.

For menuitemcheckbox this wouldn't work if multiple checkboxes are initially checked

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

selected controlled by the consumer. What do you mean by "this wouldn't work"?

@mj12albert mj12albert Jun 15, 2026

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.

Sorry not it's not specific to multiple initially checked checkboxes:

<Menu open>
  <MenuItem variant="checkbox">1</MenuItem>
  <MenuItem variant="checkbox" selected>2</MenuItem>
  <MenuItem variant="checkbox">3</MenuItem>
</Menu>

When it's coupled to roving focus's "selected", by making item 2 here initially checked, I think initial focus will land on 2 instead of 1, but the correct behavior should be: initial focus 1?

Comment on lines +184 to +188
// `menuitemcheckbox`/`menuitemradio` require `aria-checked`; derive it from `selected`
// (an omitted `selected` means unchecked). Other roles keep `selected` presentational.
const isCheckableRole = role === 'menuitemcheckbox' || role === 'menuitemradio';
const ariaChecked = isCheckableRole ? Boolean(props.selected) : undefined;

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.

I think it's a necessity as nobody will bother doing it 😆

@siriwatknp

Copy link
Copy Markdown
Member Author

@siriwatknp Did you consider adding a MenuCheckboxItem component? Setting role="menuitemcheckbox" doesn't feel like setting a deliberate prop, and menuitemcheckbox/radio don't look nice

Let me try it

@siriwatknp

siriwatknp commented Jun 15, 2026

Copy link
Copy Markdown
Member Author

@siriwatknp Did you consider adding a MenuCheckboxItem component? Setting role="menuitemcheckbox" doesn't feel like setting a deliberate prop, and menuitemcheckbox/radio don't look nice

I think exposing 2 more components are overkill, how about adding variant prop to the MenuItem?

<MenuItem variant="item"> // default
<MenuItem variant="checkbox">
<MenuItem variant="radio">

@siriwatknp siriwatknp force-pushed the feat/menuitem-checkbox-role branch from 64bda4a to 1124569 Compare June 15, 2026 03:55
@siriwatknp siriwatknp changed the title [MenuItem] Support menuitemcheckbox/menuitemradio with aria-checked [MenuItem] Add variant prop for checkbox and radio menu items Jun 15, 2026
@siriwatknp siriwatknp force-pushed the feat/menuitem-checkbox-role branch from 1124569 to d632bf6 Compare June 15, 2026 04:04
@mj12albert

Copy link
Copy Markdown
Member

how about adding variant prop to the MenuItem

Generally our variant prop indicates visual variants (e.g. primary vs secondary button) with no functional differences, but in this case both visuals and functionality are changed so I'm not sure it's the best fit?
WDYT @silviuaavram

@siriwatknp

siriwatknp commented Jun 15, 2026

Copy link
Copy Markdown
Member Author

how about adding variant prop to the MenuItem

Generally our variant prop indicates visual variants (e.g. primary vs secondary button) with no functional differences, but in this case both visuals and functionality are changed so I'm not sure it's the best fit? WDYT @silviuaavram

good point, then may be a type prop? I think using a prop is better in this case, it's just naming to decide.

well, if naming is too hard. I rather switch back to the role prop. Consumer gets autocomplete anyway, I don't think having an alias prop is worth it.

For role=menuitemcheckbox or menuitemradio, the selected prop sets aria-checked (omitted selected means unchecked); other roles keep selected presentational. No new prop, no breakage. Add docs section with menuitemcheckbox and menuitemradio demos.
@siriwatknp siriwatknp force-pushed the feat/menuitem-checkbox-role branch from d632bf6 to 8d8b43f Compare June 16, 2026 03:41
@siriwatknp siriwatknp changed the title [MenuItem] Add variant prop for checkbox and radio menu items [MenuItem] Support menuitemcheckbox/menuitemradio with aria-checked Jun 16, 2026
@siriwatknp siriwatknp changed the title [MenuItem] Support menuitemcheckbox/menuitemradio with aria-checked [MenuItem] add aria-checked for checkbox and radio menu items Jun 16, 2026
"description": "This prop can help identify which element has keyboard focus. The class name will be applied when the element gains the focus through keyboard interaction. It&#39;s a polyfill for the <a href=\"https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo\">CSS :focus-visible selector</a>. The rationale for using this feature <a href=\"https://github.com/WICG/focus-visible/blob/HEAD/explainer.md\">is explained here</a>. A <a href=\"https://github.com/WICG/focus-visible\">polyfill can be used</a> to apply a <code>focus-visible</code> class to other components if needed."
},
"selected": { "description": "If <code>true</code>, the component is selected." },
"selected": {

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.

is this API consistent with other such examples in the library?

if we want to keep this stateless, why not just create an example with both role and aria-checked passed directly by the users? we're not helping much with the selected prop, we just translate it for them.

the other option in my opinion is go stateful, and manage the checked state ourselves. in this case, we probably need a Context wrapper and maybe new component wrapping MenuItem: MenuItemCheckbox and MenuItemRadio or something.

what do you think? @siriwatknp @mj12albert

@mj12albert mj12albert Jun 16, 2026

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.

Re-using the name selected for checkbox/radio items adds indirection because you typically think of them as "checked" and not "selected".

Additionally if the API was to switch components via prop (e.g. MenuItem variant="checkbox"), "selected" could influence any of these 3 things depending on the component:

  1. styling - whether .Mui-selected is added or not
  2. the aria-checked state
  3. the initial focus target - when the selected prop is passed into the roving focus hook

which could get hard to reason about when it's coupled to combinations of the variant and selected props, does that make sense?

So having new checkbox/radio item components gives us a way out of some restrictions/shortcomings of the current API

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

Labels

package: material-ui Specific to Material UI. scope: menu Changes related to the menu. 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.

3 participants