Page MenuHomePhabricator

Investigate accessibility of checkbox hacks
Open, MediumPublic

Description

In an effort to cut down on JavaScript usage, there is more and more adoption of checkbox hack in different skins and usages (e.g. T225213, T195053, also in Vector menus). At the same time, you can read quite a lot of negative some critical feedback about this checkbox hack (given that it exists for a long time, we just were, as usual, late to adoption) from those concerned with web accessibility, see, for example, here:

Before we adopt the checkbox hack seemingly everywhere, can someone do an assessment on whether it is good to use checkbox hack in the first place? This could be, for example, accessibility testing or asking someone specialised in web accessibility for their thoughts on this kind of solution.

Example page

Possible concerns & solutions (intended)

  • Keyboard friendliness
    • <Tab> navigation reaches the button (role="button" carrying element)
    • <Tab> key navigates from the button to the menu items if and only if the menu is open
    • <Space> and <Enter> key strokes on button trigger the checkbox hack targeted element
  • The hidden checkbox needs to be associated to its label (which receives mouse/pointer events) using the label's for attribute, see https://codepen.io/Volker_E/pen/vYNPBON
    • role="button" is not on the checkbox with its inherent role="checkbox" as we shouldn't override inherent roles in ARIA land
    • Button-like element needs to carry additional aria-labelledby, specifically for the case where the label element comes after it in DOM
  • Basic interaction is provided in a no-JS environment
  • Screen-reader experience is enhanced by JS dynamically changing ARIA attributes (aria-expanded is set to true/false) to announce the menu's state.

Related Objects

StatusSubtypeAssignedTask
ResolvedGoalovasileva
ResolvedJdlrobson
Resolvedovasileva
ResolvedSpikeovasileva
ResolvedSpikephuedx
Resolvedovasileva
ResolvedSpikeVolker_E
OpenNone
OpenBUG REPORTBkudiess-msft
Resolvedovasileva
OpenNone
ResolvedSpikeovasileva
Resolvedovasileva
ResolvedBUG REPORTmatmarex
Resolvedovasileva
ResolvedJdlrobson
Resolvedphuedx
Resolvednray
ResolvedMayakp.wiki
ResolvedMayakp.wiki
Stalledovasileva
OpenNone
ResolvedEdtadros
OpenNone
OpenNone

Event Timeline

Volker_E added subscribers: Jdrewniak, LGoto.

Thanks @stjn, I was thinking along those lines, when revisiting Vector's article accessibility in T247701.

  • One immediate action might be to add aria-labelledby on the input to point to the label, but we're currently adding display:none to the checkbox, which removes it from the accessibility tree.

To the articles' concerns (mostly Hugo Giraudel's):

  • On :checked support, we have to clearly state, that we're not supporting IE 6-8 in this sense any more (specifically the combination with a screenreader), same with Opera Mini, UC, QQ or KaiOS Browser. Those are not commonly known combinations as far as I'm aware of form my insights in accessibility circles.
  • We need to evaluate if the combination of the hidden checkbox and the visible label is really working as expected for our users.
    • Could consider to add role=button to the label and additionally add aria-controls=toc-list and aria-expanded=true||false to the triggering element and aria-hidden=true||false to the ul, additionally id toc-list

I've found this resource valuable in making this hack accessible: https://inclusive-components.design/menus-menu-buttons/ section "The checkbox hack"

We followed the guidelines that @Demian mentioned in the Minvera dropdown in AMC-mode.

In that instance:

  1. we used opacity & visibility to hide the menus so that it remains in the accessibility tree.
  2. We placed the dropdown menu element right next to the input/label, so that when activated, it is presented right after the label, without having to manually manage focus.
  3. We added the aria-expanded=true/false with JS when the dropdown is open and closed.
  4. We added role=button to the checkbox input.

(We haven't yet added the aria label on the main-menu yet, it's just on the dropdowns pictured below ATM).

IMO those dropdowns are as good as it gets accessibility wise with the checkbox hack, but it would be great for someone to actually validate that opinion :P

  1. we used opacity & visibility [in Minerva] to hide the menus so that it remains in the accessibility tree.

What is the benefit of doing this? (I haven't tested it yet with screen reader.) Afaik the common practice is to display: none from the accessibility tree when the menu is closed.
Note that ATM I cannot TAB through the Minerva user menu or the main menu at all in Chrome after focusing the user icon and opening the menu with SPACE.
Also note that ENTER starts a search instead of opening the menu, so there's much to improve.

What is the benefit of doing this? (I haven't tested it yet with screen reader.) Afaik the common practice is to display: hide from the accessibility tree when the menu is closed.

A checkbox is not focusable if you are hiding it entirely with (presumably you meant) display: none;.

What is the benefit of doing this? (I haven't tested it yet with screen reader.) Afaik the common practice is to display: hide from the accessibility tree when the menu is closed.

A checkbox is not focusable if you are hiding it entirely with (presumably you meant) display: none;.

Indeed, but I wasn't talkin about the checkbox. Take a look at the code.

Right, so we don't use display:none for the checkbox because then the label is not focusable, but we also don't use display:none on the menu itself so that it's animatable with transitions, like in the code here.

Right, so we don't use display:none for the checkbox because then the label is not focusable,

I'd add that the checkbox is the actual focused element (document.activeElement), but the label also applies the :focus styles while the associated input element is focused.
@Jdrewniak This is a problem if the label is part of the header element in the DOM (it's *not* next to the checkbox) like in the POC I made for the sidebar hiding.
tabindex=0 can be used on the label, but focusing the label and pressing SPACE won't trigger a state-change on the checkbox without extra javascript...

Note: Assuming that the header will have its own element, the checkbox can't be part of the header, it must be part of the body to be able to use the sibling selector for the sidebar hiding css rules.
This complicates the positioning of the menu icon in the tab order. The checkbox should be right before the header and the label have to be before any focusable element in the header so that the tab order is as expected.

but we also don't use display:none on the menu itself so that it's animatable with transitions, like in the code here.

Thanks. That explains it.

@Volker_E Does this block the work that Stephen is doing moving this into core to support the collapsible menu? If so should we prioritize doing this?

@Volker_E given T240489 is not pressing can I interrupt the above parent task assignment as meaning this is also not pressing? If so, how do we feel about making this an explicit outcome of the spike in T240489 ? It seems strange to think about isolation in two cases.

@Volker_E what's "Connection hidden checkbox and it's label which acts as trigger element"?

@Demian If the label doesn't encapsulate the checkbox or has the for attribute correctly assigned, the trigger element doesn't check the checkbox. In former times, browsers sometimes even had issues with labels that came after the connected input in DOM. This doesn't seem to be an issue in modern browsers any more. See https://codepen.io/Volker_E/pen/vYNPBON

@Demian If the label doesn't encapsulate the checkbox or has the for attribute correctly assigned, the trigger element doesn't check the checkbox. In former times, browsers sometimes even had issues with labels that came after the connected input in DOM. This doesn't seem to be an issue in modern browsers any more. See https://codepen.io/Volker_E/pen/vYNPBON

Ah, that. I didn't understand the sentence itself. I think a more clear wording would be:
"The hidden checkbox needs to be bound to its label (which receives mouse/pointer events) using the label's for attribute."

Instead of "connected" I'd use "associated" or "attached" or "bound", but I'm not sure which one is the more common word to use. MDN calls it "labeled control for the label"... not too helpful :-), w3schools uses "bound".

For the checkbox hack's CSS selectors to work the checkbox can't be encapsulated in the label, so I've left that out too.

what's "Connection hidden checkbox and it's label which acts as trigger element"?
I think a more clear wording would be: "The hidden checkbox needs to be bound to its label (which receives mouse/pointer events) using the label's for attribute."

@Volker_E would you propose an easier-to-understand wording or what I proposed above will do?

The updated CodePen shows that separating checkbox and button-like label is possible as long as checkbox is before the targeted element in DOM (and possibly also before the label for no-JS visual state changes, also parent or sibling element). Please excuse not cleaned-up code example.
That means, the checkbox can be put far away from the button-like label and the focussed label and the triggered element can be close together.
Checkbox itself can be aria-hidden and the whole handling of screenreader and no-JS/CSS are separated concerns. For the win!

It's triggerable via <Space> and <Enter> on the label itself.

The patch for T246420 adds a jumplink to the sidebar button ("Jump to naviation"). This raises the question: what to do when either the sidebar is opened or closed.

  1. If not open already, should the sidebar open or stay closed?
  2. Should the first link receive focus or the sidebar button?
  3. What to do if JS is disabled?

IMO the jumplink should open and focus the first link. This would require JS, the no-JS fallback should just jump to the sidebar button.

If JS is disabled, the first link could be focused, but it's not worth the complication: the sidebar would need to be forced open with :focus-within selector - which wouldn't work on IE - and would double the checkboxhack selectors.

I'm not convinced we should open the sidebar after clicking "jump to navigation".

One thing that happens in that patch is that the entire sidebar is moved into the <header>element, next to the logo (this is done so that the sidebar appears after the button that opens it, in the tab order).

This change in DOM structure makes the sidebar the very first element in the navigation, whereas previously, it was the last. The previous order was personal-navigation, tabs, search, sidebar. I venture to guess that that order is still valid in terms of importance, so having to make users tab over the sidebar before landing on the other sections could be annoying (especially for e.g. given the number of languages in the sidebar). For this reason I think that having the "jump to navigation" link focus the sidebar button is the least disruptive option.

so having to make users tab over the sidebar before landing on the other sections could be annoying (especially for e.g. given the number of languages in the sidebar). For this reason I think that having the "jump to navigation" link focus the sidebar button is the least disruptive option.

Very good point, thank you!

This has sat here since June without any activity, so I'm going to assume it's not important (right now compared to other priorities) and bump it back to the backlog.