Skip to content

fix(BBBCheckbox): show keyboard focus and disabled state; add tooltip and helper text - #117

Open
Arthurk12 wants to merge 8 commits into
bigbluebutton:developfrom
Arthurk12:issue/108
Open

Arthurk12 wants to merge 8 commits into
bigbluebutton:developfrom
Arthurk12:issue/108

Conversation

@Arthurk12

Copy link
Copy Markdown
Member

What does this PR do?

BBBCheckbox now shows keyboard focus and a disabled label, and gains a tooltip and helper text.

  • Keyboard focus draws a 2px brand-colored outline around the box (square or round), like BBButton's focus outline.
  • When disabled, the label is dimmed and shows a not-allowed cursor.
  • New tooltipLabel and tooltipPlacement (top by default, bottom, left, right) props show a tooltip on hover and keyboard focus.
  • A disabled checkbox with a tooltipLabel stays reachable with Tab, so keyboard users can read why it is unavailable. It is announced as unavailable, can't be toggled, and is left out of form submission and validation, like a natively disabled checkbox.
  • New helperText prop shows secondary text below the label. Clicking it toggles the checkbox, and screen readers read it as the checkbox's description.
  • Checkboxes that don't use the new props render as before.
  • Storybook controls, stories and the README now cover disabled, tooltipLabel, tooltipPlacement and helperText.

Closes Issue(s)

Closes #108

Motivation

Keyboard focus is only a faint tint inside the box, and a disabled checkbox's label still looks clickable (#108). A disabled checkbox also has no way to tell users why it is unavailable, and there is no helper text like BBBToggle's.

More

StyledCheckbox sets `padding: 0` on the MUI root, so MUI's focus ripple
only tints the inside of the box. On a light background that is a
faint grey square, and nothing else marks keyboard focus, which falls
short of WCAG 2.4.7 (Focus Visible).

Draw a 2px colorBrand1 outline, offset by 2px, on `.Mui-focusVisible`,
matching the color BBButton uses for its focus outline. Keying it on
`.Mui-focusVisible` instead of `:focus` keeps the outline off for mouse
clicks. The root's border radius follows the shape of the control
(borderRadiusSmall for the square box, 50% for the round variant), so
the outline traces the box instead of MUI's default circle.

Restoring MUI's 9px padding was discarded: it would bring back a 39px
ripple halo and change the checkbox's layout footprint for every
consumer.

Refs bigbluebutton#108
…sabled

The component does not pass `disabled` to its Label, which always has
`cursor: pointer` and the default text color. With `disabled`, MUI
dims the box, but the label still looks and points as if it were
clickable.

Destructure `disabled`, pass it on to the MUI checkbox and, as a
transient `$disabled` prop, to the Label. A disabled label gets
`cursor: not-allowed` and colorNeutral3, the same color BBButton uses
for disabled text. The label is built once and placed according to
`layout`, instead of being duplicated per layout.

Also document `disabled` in the Storybook controls and in the README
props table, and add a Disabled story.

Refs bigbluebutton#108
BBBCheckbox has no way to show a tooltip. When a checkbox is disabled,
users get no explanation of why they can't change it. BBButton and
BBBAccordion already offer this through `tooltipLabel` and
`tooltipPlacement`.

Add the same two props, backed by Tippy like the other components.
The tooltip is attached to the container around the box and the
label, not to the input: MUI gives a disabled box `pointer-events:
none`, so hovering it reaches the container. It opens on `mouseenter`
and `focusin`, because `focusin` bubbles up from the input and Tippy's
`focus` trigger does not. The tooltip text is also kept in a visually
hidden element that the input's `aria-describedby` points to, since
Tippy only mounts its content while the tooltip is shown.

A natively disabled input leaves the tab order, so keyboard users
could never reach the tooltip that explains why it is disabled. When
`disabled` and `tooltipLabel` are both set, the input gets
`aria-disabled="true"` instead of the native `disabled` attribute and
stays focusable:
- the MUI checkbox still receives `disabled`, so the box keeps its
  disabled look;
- an `onClick` with `preventDefault()` on the checkbox root cancels
  mouse clicks, label clicks and Space, and MUI skips `onChange` for a
  prevented click. It sits on the root because MUI drops event
  handlers passed through `slotProps.input`, except `onChange`;
- `name` and `required` are dropped from the input, so it stays out
  of form submission and validation, as a natively disabled input
  would. Without that, a required, unchecked, aria-disabled checkbox
  would block its form from being submitted;
- MUI never sets `Mui-focusVisible` while disabled, so the focus
  outline matches it through `.Mui-disabled:has(input:focus-visible)`.

Without a tooltip, a disabled checkbox keeps the native `disabled`
attribute and leaves the tab order, as before. Making the container
focusable was discarded: focus would land on an element with no role
or name, which screen readers announce poorly.

Refs bigbluebutton#108
BBBCheckbox has no way to show secondary text under its label, which
BBBToggle offers through `helperText`. Consumers that need a short
explanation next to a checkbox have to lay it out themselves.

Add `helperText`, rendered below the label in fontSizeSmall and
colorTextLight (one step below the label, as in BBBToggle), and dimmed
like the label when disabled.

The container turns from an inline flex row into an inline grid, with
the box and the label on the first row and the helper text on the
second, under the label. In a flex row, the box would be centered on
the whole two-line text block instead of on the label's line. With
the `left` layout, the text is aligned to the end, next to the box.
Layouts without helper text render the same as before.

The helper text is a second <label> for the input, so clicking it
toggles the checkbox, as clicking the label does; in a disabled
checkbox the click does nothing. Every associated <label> joins the
accessible name, which would read the helper text twice. When there is
a label, the input's aria-labelledby points at the label alone and
aria-describedby at the helper text, followed by the tooltip text when
`tooltipLabel` is set. With no label or `ariaLabel`, the helper text
names the checkbox and is left out of the description. BBBToggle uses
the same rule.

Also document `helperText` in the Storybook controls and in the
README, and add stories with the helper text on each side.
@Arthurk12
Arthurk12 changed the base branch from main to develop November 10, 2026 00:24

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

Checked the focus ring, disabled styling, the aria-disabled path (no toggle on click/label/helper/Space, left out of form data and validation) and names/descriptions in Chromium, Firefox and WebKit on MUI 6.4.9, 6.5.0 and 7.3.4. All good apart from the inline notes.

For the description: a wrapped label with layout="left" is now right-aligned and the hover tint is a rounded square, so "render as before" isn't quite exact (both look fine).

Comment thread src/components/Checkbox/component.tsx Resolved
Comment thread src/components/Checkbox/component.tsx Resolved
Comment thread src/components/Checkbox/component.tsx Resolved
Comment thread src/components/Checkbox/component.tsx Resolved
feat(BBBCheckbox): add tooltipLabel and tooltipPlacement props

BBBCheckbox has no way to show a tooltip. When a checkbox is disabled,
users get no explanation of why they can't change it. BBButton and
BBBAccordion already offer this through `tooltipLabel` and
`tooltipPlacement`.

Add the same two props, backed by Tippy like the other components.
The tooltip is attached to the container around the box and the
label, not to the input: MUI gives a disabled box `pointer-events:
none`, so hovering it reaches the container. It opens on `mouseenter`
and `focusin`, because `focusin` bubbles up from the input and Tippy's
`focus` trigger does not. Tippy is rendered beside the container and
pointed at it through `reference`, instead of wrapping it: wrapping
would change the root element whenever `tooltipLabel` is added or
removed, and React would remount the checkbox, dropping its focus and
resetting an uncontrolled checkbox to `defaultChecked`.

The tooltip text is also kept in a `hidden` element that the input's
`aria-describedby` points to, since Tippy only mounts its content
while the tooltip is shown. A visually hidden copy was discarded: it
is still page text, so screen readers would read the tooltip twice in
browse mode.

A natively disabled input leaves the tab order, so keyboard users
could never reach the tooltip that explains why it is disabled. When
`disabled` and `tooltipLabel` are both set, the input gets
`aria-disabled="true"` instead of the native `disabled` attribute and
stays focusable:
- the MUI checkbox still receives `disabled`, so the box keeps its
  disabled look;
- an `onClick` with `preventDefault()` on the checkbox root cancels
  mouse clicks, label clicks and Space, and MUI skips `onChange` for a
  prevented click. It sits on the root because MUI drops event
  handlers passed through `slotProps.input`, except `onChange`;
- `name` and `required` are dropped from the input, so it stays out
  of form submission and validation, as a natively disabled input
  would. Without that, a required, unchecked, aria-disabled checkbox
  would block its form from being submitted;
- MUI never sets `Mui-focusVisible` while disabled, so the focus
  outline matches it through `.Mui-disabled:has(input:focus-visible)`.

Without a tooltip, a disabled checkbox keeps the native `disabled`
attribute and leaves the tab order, as before. Making the container
focusable was discarded: focus would land on an element with no role
or name, which screen readers announce poorly.

Refs bigbluebutton#108
feat(BBBCheckbox): add tooltipLabel and tooltipPlacement props

BBBCheckbox has no way to show a tooltip. When a checkbox is disabled,
users get no explanation of why they can't change it. BBButton and
BBBAccordion already offer this through `tooltipLabel` and
`tooltipPlacement`.

Add the same two props, backed by Tippy like the other components.
The tooltip is attached to the container around the box and the
label, not to the input: MUI gives a disabled box `pointer-events:
none`, so hovering it reaches the container. It opens on `mouseenter`
and `focusin`, because `focusin` bubbles up from the input and Tippy's
`focus` trigger does not. Tippy is rendered beside the container and
pointed at it through `reference`, instead of wrapping it: wrapping
would change the root element whenever `tooltipLabel` is added or
removed, and React would remount the checkbox, dropping its focus and
resetting an uncontrolled checkbox to `defaultChecked`.

The tooltip text is also kept in a `hidden` element that the input's
`aria-describedby` points to, since Tippy only mounts its content
while the tooltip is shown. A visually hidden copy was discarded: it
is still page text, so screen readers would read the tooltip twice in
browse mode.

When nothing else names the checkbox (no `label` or `ariaLabel`), the
tooltip text names it through `aria-labelledby` instead of describing
it, so a checkbox with only a tooltip still has an accessible name.

A natively disabled input leaves the tab order, so keyboard users
could never reach the tooltip that explains why it is disabled. When
`disabled` and `tooltipLabel` are both set, the input gets
`aria-disabled="true"` instead of the native `disabled` attribute and
stays focusable:
- the MUI checkbox still receives `disabled`, so the box keeps its
  disabled look;
- an `onClick` with `preventDefault()` on the checkbox root cancels
  mouse clicks, label clicks and Space, and MUI skips `onChange` for a
  prevented click. It sits on the root because MUI drops event
  handlers passed through `slotProps.input`, except `onChange`;
- `name` and `required` are dropped from the input, so it stays out
  of form submission and validation, as a natively disabled input
  would. Without that, a required, unchecked, aria-disabled checkbox
  would block its form from being submitted;
- MUI never sets `Mui-focusVisible` while disabled, so the focus
  outline matches it through `.Mui-disabled:has(input:focus-visible)`.

Without a tooltip, a disabled checkbox keeps the native `disabled`
attribute and leaves the tab order, as before. Making the container
focusable was discarded: focus would land on an element with no role
or name, which screen readers announce poorly.

Refs bigbluebutton#108
feat(BBBCheckbox): add helperText prop

BBBCheckbox has no way to show secondary text under its label, which
BBBToggle offers through `helperText`. Consumers that need a short
explanation next to a checkbox have to lay it out themselves.

Add `helperText`, rendered below the label in fontSizeSmall and
colorTextLight (one step below the label, as in BBBToggle), and dimmed
like the label when disabled.

The container turns from an inline flex row into an inline grid, with
the box and the label on the first row and the helper text on the
second, under the label. In a flex row, the box would be centered on
the whole two-line text block instead of on the label's line. With
the `left` layout, the text is aligned to the end, next to the box, so
a label that wraps onto several lines is now right-aligned even
without helper text.

The helper text is a second <label> for the input, so clicking it
toggles the checkbox, as clicking the label does; in a disabled
checkbox the click does nothing. Every associated <label> joins the
accessible name, which would read the helper text twice. When there is
a label, the input's aria-labelledby points at the label alone and
aria-describedby at the helper text, followed by the tooltip text when
`tooltipLabel` is set. With no label or `ariaLabel`, the helper text
names the checkbox and is left out of the description, and the
tooltip text, which would otherwise name it, stays in the description.
BBBToggle uses the same rule.

Also document `helperText` in the Storybook controls and in the
README, and add stories with the helper text on each side.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BBBCheckbox: no visible keyboard focus, and the label ignores disabled

2 participants