Session and project rows in the sidebar render an interactive control inside the row's own button element. React reports it on every render of the session list:
In HTML, <button> cannot be a descendant of <button>.
The nesting comes from SideNavItem's API, not from a misuse. Its endContent is rendered inside the element that carries the row's own interaction (SideNavItem.js:345, within NavItemElement's children), so any interactive endContent lands inside the row button:
SessionNavRow → SideNavItem → NavItemElement → <button>
└─ <span> (endContent)
└─ MoreMenu → Button → <button>
Both call sites are affected: session rows (session-history-list.tsx:668) and project rows (session-history-list.tsx:585), each passing a MoreMenu for row actions.
We already carry a mitigation. EndContentHitTarget (session-history-list.tsx:371) wraps the trailing controls and stops click, pointerdown and keydown from reaching the row — its comment reads "Keeps trailing controls from activating the parent SideNavItem button". That suppresses the misfire, but the invalid nesting remains, and it is not only console noise:
- Nested interactive elements have no defined activation behaviour; browsers and AT resolve them inconsistently.
- The row and its menu are separate tab stops inside one control, which is what WCAG 4.1.2 is about.
stopPropagation on keydown is a blunt instrument — it also blocks keys the row legitimately handles.
Astryx solved the same shape elsewhere in 0.3.0. CheckboxList moved to useClickableContainer: the row becomes a plain container that delegates surface clicks to the one control inside it, which stays the single tab stop. That release added interactiveRef to Item/ListItem specifically to support this, replacing an internal invisible row button. SideNavItem has not adopted the pattern.
Two directions, not exclusive:
- Upstream — ask for
useClickableContainer/interactiveRef on SideNavItem, so endContent can hold controls without nesting. This is the fix that removes the class of bug. - Local — stop routing interactive
endContent through SideNavItem; render MoreMenu as a sibling of the row and position it over the row's trailing edge. Removes the nesting today and lets EndContentHitTarget go with it.
Reproduce: run Storybook, open Product/Shell Official AppShell → Native Conversation, and read the console — the error fires once per session row.
Found while working on #2358, which does not touch this file.
Session and project rows in the sidebar render an interactive control inside the row's own button element. React reports it on every render of the session list:
The nesting comes from
SideNavItem's API, not from a misuse. ItsendContentis rendered inside the element that carries the row's own interaction (SideNavItem.js:345, withinNavItemElement's children), so any interactiveendContentlands inside the row button:Both call sites are affected: session rows (
session-history-list.tsx:668) and project rows (session-history-list.tsx:585), each passing aMoreMenufor row actions.We already carry a mitigation.
EndContentHitTarget(session-history-list.tsx:371) wraps the trailing controls and stopsclick,pointerdownandkeydownfrom reaching the row — its comment reads "Keeps trailing controls from activating the parent SideNavItem button". That suppresses the misfire, but the invalid nesting remains, and it is not only console noise:stopPropagationonkeydownis a blunt instrument — it also blocks keys the row legitimately handles.Astryx solved the same shape elsewhere in 0.3.0.
CheckboxListmoved touseClickableContainer: the row becomes a plain container that delegates surface clicks to the one control inside it, which stays the single tab stop. That release addedinteractiveReftoItem/ListItemspecifically to support this, replacing an internal invisible row button.SideNavItemhas not adopted the pattern.Two directions, not exclusive:
useClickableContainer/interactiveRefonSideNavItem, soendContentcan hold controls without nesting. This is the fix that removes the class of bug.endContentthroughSideNavItem; renderMoreMenuas a sibling of the row and position it over the row's trailing edge. Removes the nesting today and letsEndContentHitTargetgo with it.Reproduce: run Storybook, open
Product/Shell Official AppShell → Native Conversation, and read the console — the error fires once per session row.Found while working on #2358, which does not touch this file.