Describe the bug
BBBToggle with label and helperText exposes the wrong accessible name, description and role to assistive technology:
- The helper text becomes part of the accessible name. The
<label htmlFor={id}> (Styled.TextWrapper) wraps both the title span and the helper span. The browser therefore names the input from the whole label content.
aria-labelledby / aria-describedby / aria-label never reach the <input>. Toggle passes them as root props of MUI Switch. SwitchBase forwards unknown root props to its ButtonBase span, not to the input. The ${id}-label / ${id}-helper wiring has no effect.
- The role is
checkbox, not switch. MUI Switch renders <input type="checkbox"> without role="switch", so screen readers announce a checkbox.
The same structure is still on main (src/components/Toggle/component.tsx, last touched in cb49ba4), so this isn't specific to the version below.
To Reproduce
<BBBToggle
checked={false}
onChange={() => {}}
label="Hide participants without camera"
helperText="Show only active cameras"
textPosition="right"
/>
Inspect the accessibility tree (Chrome DevTools → Accessibility, or Playwright ariaSnapshot()).
Expected behavior
- Name:
Hide participants without camera
- Description:
Show only active cameras
- Role:
switch
aria-labelledby / aria-describedby / aria-label set on the <input>
Actual behavior
Playwright ariaSnapshot() of the rendered toggle:
- checkbox "Hide participants without camera Show only active cameras"
DOM, where the aria attributes end up:
<span class="MuiButtonBase-root MuiSwitch-switchBase ..."
aria-labelledby=":r8:-label" aria-describedby=":r8:-helper">
<input type="checkbox" id=":r8:" class="MuiSwitch-input ...">
getByRole('switch') finds nothing. getByRole('checkbox', { name: 'Hide participants without camera Show only active cameras' }) matches.
Suggested fix
- Pass the aria props through
inputProps (or slotProps.input) so they land on the <input>, and add role: 'switch' there.
- Keep only the title inside the
<label htmlFor>, and render the helper text as a sibling referenced by aria-describedby. Clicking the helper text can stay wired through its own click handler if that behavior matters.
Workaround used downstream in the meantime: pass a fixed id and inputProps={{ 'aria-labelledby': ${id}-label, 'aria-describedby': ${id}-helper, role: 'switch' }}. That still leaves the helper text inside the label.
BBB version, plugin in which the bug happened and SDK version:
@bigbluebutton/bbb-ui-components-react 0.7.0; source unchanged on main (v2.3.0)
@mui/material 6.1.6
- BigBlueButton 4.0 HTML5 client (the mconf-live
develop branch): a new "Layout view" popover in the actions bar, not a plugin
Desktop (please complete the following information):
- OS: Linux (Ubuntu 22.04)
- Browser: Chromium (Playwright build 1194), headless
Additional context
Found in an accessibility review of a client feature that uses BBBToggle in a popover. The component's own intent (labelId / helperTextId and the ariaLabelledBy / ariaDescribedBy props) is right; the attributes just end up on the wrong element.
Describe the bug
BBBTogglewithlabelandhelperTextexposes the wrong accessible name, description and role to assistive technology:<label htmlFor={id}>(Styled.TextWrapper) wraps both the title span and the helper span. The browser therefore names the input from the whole label content.aria-labelledby/aria-describedby/aria-labelnever reach the<input>.Togglepasses them as root props of MUISwitch.SwitchBaseforwards unknown root props to itsButtonBasespan, not to the input. The${id}-label/${id}-helperwiring has no effect.checkbox, notswitch. MUISwitchrenders<input type="checkbox">withoutrole="switch", so screen readers announce a checkbox.The same structure is still on
main(src/components/Toggle/component.tsx, last touched in cb49ba4), so this isn't specific to the version below.To Reproduce
Inspect the accessibility tree (Chrome DevTools → Accessibility, or Playwright
ariaSnapshot()).Expected behavior
Hide participants without cameraShow only active camerasswitcharia-labelledby/aria-describedby/aria-labelset on the<input>Actual behavior
Playwright
ariaSnapshot()of the rendered toggle:DOM, where the aria attributes end up:
getByRole('switch')finds nothing.getByRole('checkbox', { name: 'Hide participants without camera Show only active cameras' })matches.Suggested fix
inputProps(orslotProps.input) so they land on the<input>, and addrole: 'switch'there.<label htmlFor>, and render the helper text as a sibling referenced byaria-describedby. Clicking the helper text can stay wired through its own click handler if that behavior matters.Workaround used downstream in the meantime: pass a fixed
idandinputProps={{ 'aria-labelledby':${id}-label, 'aria-describedby':${id}-helper, role: 'switch' }}. That still leaves the helper text inside the label.BBB version, plugin in which the bug happened and SDK version:
@bigbluebutton/bbb-ui-components-react0.7.0; source unchanged onmain(v2.3.0)@mui/material6.1.6developbranch): a new "Layout view" popover in the actions bar, not a pluginDesktop (please complete the following information):
Additional context
Found in an accessibility review of a client feature that uses
BBBTogglein a popover. The component's own intent (labelId/helperTextIdand theariaLabelledBy/ariaDescribedByprops) is right; the attributes just end up on the wrong element.