Description
MenuItem hardcodes role="menuitem" on its inner button/link element and role="none" on its <li> wrapper. There is no prop to override these roles.
Consumers of ChatbotConversationHistoryNav can override the <ul> role via menuProps (e.g., menuProps={{ role: 'list' }}), but the child MenuItem roles remain hardcoded regardless of the parent menu role.
Per the WAI-ARIA spec, role="menuitem" is only valid inside role="menu" or role="menubar". When a consumer overrides the parent to role="list", the remaining role="menuitem" on child elements becomes an ARIA violation.
User impact
This was discovered during an accessibility audit. With the default role="menu" on the <ul>, screen readers announce "Press escape to close the menu" when users navigate into the conversation history list. However, pressing Escape does nothing — the list is not a dismissible menu widget, it's a list of conversations inside a drawer panel. This is confusing for screen reader users who expect the announced behavior to work.
On Ask App we can fix the <ul> role via menuProps={{ role: 'list' }}, which eliminates the "Press escape" announcement. But the inner role="menuitem" on buttons remains, causing screen readers to still announce each conversation as a "menu item" rather than a simple button — which is misleading when the container is no longer a menu.
Current behavior
code:
<ChatbotConversationHistoryNav
menuProps={{ role: 'list', 'aria-label': 'Chat history' }}
// ...
/>
Rendered accessibility tree:
✅ overridden by consumer via menuProps
- ❌ hardcoded by MenuItem
❌ hardcoded by MenuItem — ARIA violation (menuitem outside of menu)
Conversation title
Expected behavior
When the parent menu role is not `"menu"` or `"menubar"`, `MenuItem` should not force menu-specific roles on its elements:
- implicit role: listitem
implicit role: button
Conversation title
Proposed solution
In `MenuItem`, the role is currently set unconditionally:
// On the
:
role: !hasCheckbox ? 'none' : 'menuitem'
// On the inner element:
role: isSelectMenu ? 'option' : 'menuitem'
This could check the `role` from `MenuContext` and only apply menu-specific roles when appropriate:
const isMenuRole = menuRole === 'menu' || menuRole === 'menubar';
// On the
:
role: isMenuRole ? (!hasCheckbox ? 'none' : 'menuitem') : undefined
// On the inner element:
role: isMenuRole ? (isSelectMenu ? 'option' : 'menuitem') : undefined
This fix is backward-compatible — it only changes behavior when the parent role is something other than "menu" or `"menubar"`, so existing consumers are unaffected.
### Steps to Reproduce
1. Use `ChatbotConversationHistoryNav` with `menuProps={{ role: 'list' }}`
2. Open the conversation history drawer
3. Inspect the accessibility tree
4. `<li>` elements still have `role="none"` and `<button>` elements still have `role="menuitem"` despite the parent `<ul>` no longer being `role="menu"`
5. With a screen reader, each conversation is announced as a "menu item" instead of a "button"
### Data/JSON Context (if applicable)
_No response_
### Environment
_No response_
### Screenshots or Logs
_No response_
Jira Issue: PF-3992
Description
MenuItemhardcodesrole="menuitem"on its inner button/link element androle="none"on its<li>wrapper. There is no prop to override these roles.Consumers of
ChatbotConversationHistoryNavcan override the<ul>role viamenuProps(e.g.,menuProps={{ role: 'list' }}), but the childMenuItemroles remain hardcoded regardless of the parent menu role.Per the WAI-ARIA spec,
role="menuitem"is only valid insiderole="menu"orrole="menubar". When a consumer overrides the parent torole="list", the remainingrole="menuitem"on child elements becomes an ARIA violation.User impact
This was discovered during an accessibility audit. With the default
role="menu"on the<ul>, screen readers announce "Press escape to close the menu" when users navigate into the conversation history list. However, pressing Escape does nothing — the list is not a dismissible menu widget, it's a list of conversations inside a drawer panel. This is confusing for screen reader users who expect the announced behavior to work.On Ask App we can fix the
<ul>role viamenuProps={{ role: 'list' }}, which eliminates the "Press escape" announcement. But the innerrole="menuitem"on buttons remains, causing screen readers to still announce each conversation as a "menu item" rather than a simple button — which is misleading when the container is no longer a menu.Current behavior
code:
✅ overridden by consumer via menuProps- ❌ hardcoded by MenuItem
❌ hardcoded by MenuItem — ARIA violation (menuitem outside of menu)
Conversation title
// On the
role: !hasCheckbox ? 'none' : 'menuitem'
// On the inner element:
role: isSelectMenu ? 'option' : 'menuitem'
const isMenuRole = menuRole === 'menu' || menuRole === 'menubar';
// On the
role: isMenuRole ? (!hasCheckbox ? 'none' : 'menuitem') : undefined
// On the inner element:
role: isMenuRole ? (isSelectMenu ? 'option' : 'menuitem') : undefined
Jira Issue: PF-3992