Mitigating an OpenScholar accordion mystery

The current (Drupal 7) OpenScholar Accordion widget is decent, from an accessibility perspective, but it has a glaring problem for sighted keyboard users.

A Problem

If a panel in the accordion contains elements in the tab order, typically links, they remain in the tab order, even when the panel is collapsed. When these visually-hidden controls have focus, a user can't tell where focus is within the page, it has disappeared.

This is compounded by the fact that the accordion widget has been coded following a common ARIA tabs widget pattern. In this tabs widget pattern, each visual tab is not in the Tab keypress order, instead arrow keys are used to select the corresponding tabpanel to open. This can be good for keyboard-only users when there are many tabs because it means it's one Tab keypress to reach the entire widget and the next Tab keypress goes to the next focusable element after the widget in the tab order. But in this case, next Tab keypress does not go to the next focusable element after the widget, visible focus is "lost" amongst all the focusable elements within the accordion's panels and isn't visible again until they've all been tabbed through and the widget is exited. (I don't think the ARIA roles used in tabs widget patterns are appropriate for an accordion but that's beside the point).

At first glace and the accordion's code, I was confused because right in the HTML for the div that contains the panel is style="display: none;". display: none; is one of the CSS property values that affects keyboard and assistive technology use; the content contained within the element should be unavailable to the keyboard or to assistive technology. And yet in this case, the collapsed panel contents are still available. What gives? Looking closer with the browser inspector, I found the culprit:

.accordion .ui-widget-content.os-boxes-accordion-loadfix {
    box-sizing: border-box !important;
    width: 100% !important;
    display: block !important;
    position: absolute !important;
    clip: rect(1px 1px 1px 1px);
    clip: rect(1px,1px,1px,1px);
    overflow: hidden;
    height: 1px;
    z-index: -1;
}

The critical property is display: block !important;, normally the inline style would supersede one from a stylesheet but the use of !important changes that. Most of the other properties in the selector are common for making an element only visually hidden. The use of "loadfix" in the class name makes me wonder if this was added later to address an issue; perhaps some site users didn't like that display: none; meant the collapsed content couldn't be found when users searched within the page using the browser's Control/Command-F.

A Solution :focus-within

I haven't seriously considered how best to address the root cause but I think I've come up with a pretty decent way to mitigate the problem using only custom CSS that can be added to a site:

.accordion .ui-widget-content.os-boxes-accordion-loadfix:focus-within {
    position: initial !important;
    clip: auto;
    height: auto;
    z-index: auto;
    overflow: auto;
}

:focus-within is a pseudo-class, like :focus, but instead of being active when the element itself has focus, it's also active when any of the element's children have focus. When any of the links (or other focusable elements) receive focus within a collapsed panel, this selector supersedes the properties that make the panel visually hidden; with the panel on display, users can see the focused element.

This solution is imperfect, the tab preceding the panel does not change to the expanded state (changing the arrow pointing direction from right to down). A JavaScript-based solution could listen for each focusable element receiving focus then change the tab and tabpanel states accordingly. But this CSS-only solution achieves the most important goal, showing the content where the focus is.

:focus-within Support

Support for the :focus-within pseudo-class is pretty good, the "evergreen" browsers have supported it since 2017, about as long as CSS Grid. As is typical, it's not supported in Internet Explorer and not in Microsoft Edge before it switched to Chromium. I think polyfill code is relatively lightweight to operate but it does require adding a class to elements where it's needed. Even if this solution doesn't work in all browsers, it will work for the large majority using browsers that support it, they will be better off than if nothing is done, and where the solution is not supported, they will be no worse off.