Reading List

On popover accessibility: what the browser does and doesn’t do from hiddedevries.nl Blog RSS feed.

On popover accessibility: what the browser does and doesn’t do

One of the premises of the new popover attribute is that it comes with general accessibility considerations “built in”. What does “built in accessibility” actually mean for browsers that support popover?

NOTE: except for this note, this whole post was co-written with Scott O’Hara (thanks Scott!). See also Scott's post, popover accessibility. Whether you're a developer, designer or accessibility specialist, hearing “accessibility is built in” probably makes you want to know what exactly is built-in. For popover, this actually changed quite a bit over time, after discussions at Open UI and with WHATWG. At first, the plan was to introduce a popup element with built-in roles. Later, an attribute ended up making more sense (more on that in the post). For that attribute, and thanks to the great effort of Scott and others, some “accessibility guardrails” have now emerged. And they shipped in most browsers. I hope this post helps you understand better what accessibility is “built-in” when you use popover, and what is not.

In this post

  1. Accessibility semantics
  2. What browsers do (aria-expanded, aria-details, group, keyboard accessibility)
  3. What browsers don't do
  4. Conclusion

Accessibility semantics

The “built-in” accessibility of popover is in the addition of guardrails: browsers try to improve accessibility where they can. These guardrails exist mostly in the form of browsers augmenting accessibility semantics. Before we get into what those guardrails are, let's clarify what that term means.

Many features of HTML have some amount of accessibility semantics associated with them - e.g., roles, states and properties. This is information that a web page exposes, which browsers then pass on to platform accessibility APIs. They do this, so that assistive technologies can build UIs around them (see: How accessibility trees inform assistive tech). These semantics are sometimes baked into native HTML elements. For instance, headings and lists have implicit roles (heading and list, respectively). Other elements, like the checkbox input type, have an implicit role as well as additional states and properties. Developers can use HTML elements with such “built-in” semantics. But they can also set, overwrite and augment accessibility semantics more directly in their HTML structure, using WAI-ARIA.

The thing with the popover attribute is that it doesn’t have a built-in role. After all, it’s not an element. Its purpose is to only add “popover behaviour”, as discussed in Popover semantics. In that sense, popover is a bit like tabindex or contenteditable. These attributes also add behaviour: tabability and editability behaviours, respectively.

A major reason for this choice is that there are a number of components that exhibit popover behaviours. Examples include menus, “toast” messages, sub-navigation lists of links and tooltips. You can use popover on a specific element, then it will get that element's role. Or you can use it with a generic element, and add a role that best matches what you are building.

So, while the default role is ‘generally’ not handled by the attribute (more on that later), there are other semantics (properties and states) that the attribute will expose. Browsers can take care of those with some degree of confidence.

What browsers do

There are two semantics that the browser should take care of when you use popover, and its associated popovertarget attribute. Additionally, there is some keyboard focus behaviour that may also be handled automatically, depending on the type of popover you are using.

The aria-expanded state

First, aria-expanded. This state is exposed on the element that invokes the popover, currently limited to buttons (for a variety of reasons that would require a whole other article to talk about - so this is all you get right now). When a popover is invoked by / associated with a button with the popovertarget attribute, the browser will automatically convey whether the popover is in the expanded (rendered) state, or if it is in the collapsed (hidden) state. This is implemented in Edge, Chrome, Firefox and Safari.

For the following example, the ‘heyo’ button will automatically convey whether its associated popover list is in the expanded or collapsed state, based on whether the popover list is invoked as a popover.

<button popovertarget=p>
  Heyo
</button><ul 
  aria-label="Heyo subpages" 
  id=p 
  popover
></ul>

Note: the state won’t be applied if script, rather than the declarative attribute, does the opening on click of any button (or any other element). Needless to say: it also doesn’t work if there isn’t an invoking button, for instance, and script invokes this popover (because in that case, there isn’t any expanding going on). Additionally, if you force open your popover using CSS display block, then it will not be rendered as a popover - and thus the button will still communicate that the “popover” is in the collapsed state. Also, if you’re doing that - forcing your popover open with CSS - maybe you have some things you need to reconsider with your UI.

The aria-details relationship

When the popover doesn’t immediately follow its invoking button in the accessibility tree, browsers are supposed to create an aria-details relationship on the popover’s invoking button with its associated popover. At the time of writing, this is implemented in Chrome, Edge and Firefox.

For instance, in the following markup snippet an implicit aria-details relationship will be made with the button that invokes the popover, because the button and the popover are not immediate siblings in the accessibility tree.

<button popovertarget=foo>something</button>

<p>...</p>

<div role=whatever popover id=foo>...</div>

Similarly, an aria-details relationship will be made with the next markup snippet too, because even though the popover and its invoking button are siblings, the popover is a previous sibling to the invoking element, and it might not be understood which element is the popover, because it doesn’t immediately follow the element that invoked it.

<div role=whatever popover id=foo>...</div>

<button popovertarget=foo>something</button>

In contrast, the next two examples have no aria-details association because that would be unnecessary. For the first, the popover is the immediate next sibling in the accessibility tree (note divs are generic and often ignored when they do not provide information important to accessibility). For the second, the button is a descendant of the popover, so browsers do not need to tell users that the button they are interacting with is about the context they are within. That’d be silly.

<!--  
  example 1: 
  popover immediate sibling in acc tree 
-->
<button popovertarget=m>something</button>
<div class=presentational-styles-only><div role=menu popover id=m>...</div>
</div>

<!-- 
  example 2:
  button descendant of popoover
-->

<dialog popover id=d>
  <button popovertarget=d>close</button></dialog>

For more information on how aria-details works, check out the details in the ARIA spec.

Note: aria-details is often confused with aria-describedby. That makes sense, “details” and “descriptions” are very similar. However, these two properties expose different information. aria-describedby takes the associated element’s content, flattens it into a single text string, and exposes that as the ‘description’ or ‘hint’ of the element which the attribute is specified. In contrast, aria-details only informs a user that there is additional information about the current element. That might not seem useful, until you know that screen readers largely provide quick-keys to navigate to and from that associated element which provides the details.

At the time of writing, navigating into the referenced content using quick-keys is supported by JAWS and NVDA (release notes), but not VoiceOver.

Here’s a quick demo of that with JAWS 2023 in Edge 124. JAWS lets us jump to the details content if we press Alt + Ins + D:

In NVDA (2023.3.4), tested with Edge 124, it works slightly differently: when you press the shortcut (NVDA + D), we don't jump to the details content, but it is read out after the shortcut is pressed:

(see demo on CodePen; note: announcements and shortcuts depend on the user's settings, versions, etc)

In the following demo, you can see how the aria-details relationship works between popovers and their invokers (in JAWS 2023 with Edge 124):

(video contains screenshot of code, see demo on CodePen)

In summary: the aria-details association is not announced by JAWS when we focus the invoking button for the first time. This is because the corresponding popover is hidden, so the association isn't made yet. After we open the popover, JAWS announces the button's “has details” association while it is open, to hear it we navigate away and back. This is also how it works in NVDA, which, in addition, also requires you to switch to forms mode to actually hear the relationship announced.

Warning: even if the aria-details association is implemented, it may not be completely ironed out in how the UX behaves for people. For instance, there isn't currently a way for users to find out about the details relationship once it is established, like when the popover opened. It requires for the user to move away from the button and return to it, at which point the relationship is announced. Maybe it would be helpful if the browser would fire some kind of event, to let AT like JAWS know that an element representing details has appeared.

We mention this not to deter you from using popover or to indicate that anyone is doing something “wrong” here. Rather, this is a relatively new feature that people still need to figure out some of the UX kinks around. Feedback is welcome, and to help ensure the best UX is provided, please reach out to the necessary browsers / AT with your thoughts.

The group role

As mentioned above, popover can be used on any element, including elements that don’t have a built-in role, like div. But even without a role, it’s likely that the contents of a popover form some kind of meaningful whole. This is why in Chrome, Edge and Firefox, a role of group is automatically added to popovers if they would otherwise have no role, or a role of generic (for instance, divs and spans).

The group role is added, so that assistive technology can have the option to expose the boundaries of the popover that is being displayed. This can be important to users, because a popover is a behavior and visual treatment of its content. How is one to know where such content begins or ends if it doesn’t have boundaries to expose?

It’s important to know that an unnamed group is often ignored by screen readers. This is because otherwise the Internet would be riddled with unhelpful “group” announcements. (See also why Webkit made the decision to remove list semantics from lists that have been styled to not look like lists. These topics are related). Here though, it again comes down to what assistive technology wants to do. By exposing the group role for the popover, now the element can be named by authors, which will force the group role to be exposed in most cases. Then, if AT decided they want to do something special for popover groups, they now have the opportunity to do so.

Keyboard accessibility

One more aspect of “built-in accessibility” that browsers do for your popover, is take care of some keyboard behaviors.

Moving focus back to invoking element

Edge/Chrome, Firefox and Safari will all return focus to the invoking element when you close the popover (only if you are inside of it). This is useful, because if focus was on an element inside the popover, the default would be to return focus to the start of the document. Various groups of users would get lost, increasingly so on pages with a lot of content. Moving focus back to the invoking element helps ensure people can quickly return to what they were doing, rather than spending time having to re-navigate to where they think they were last.

Popover content in tab order

Desktop browsers also do something else: they add the popover content into the tab order just after the invoking button. Even if that’s not where the popover is in the DOM.

Imagine this DOM structure:

<button popovertarget=p>Open popover</button>

<p>content… content… <a href="#">link 1</a></p>

<p>content… content… <a href="#">link 2</a></p>

<div popover id="p"><a href="#">link 3</a></div>

When the popover opens, and you press Tab, you might think you’d jump to “link 1”, the next interactive element in the DOM. Except, in desktop browsers, you will jump to “link 3” instead. The browser basically moves the popover content’s position in tab order to just after its invoking button. This takes it out of its expected position in the tab order. That improves the user experience, because it is likely that upon opening the popover, users will want to interact with its contents.

Keep in mind: browsers adjust the Tab order for instances like this, but they don't adjust the placement of the content in the accessibility tree. This is why the aria-details association was implemented. This special Tab order behavior helps ensure logical focus order for keyboard accessibility. However, we should still strive to make sure our popovers come after the invoking element in the DOM.

But since there will be times where the exact location of the popover in the DOM may be out of one’s control, this behavior is still quite welcome. For instance, if the popover happens to be far away in the DOM, having to go through the rest of the document before reaching the popover would be a nuisance. It would be highly unexpected and unwanted to have to navigate through all other focusable elements in the DOM, prior to the popover one just opened. WCAG 2.4.3 Focus Order requires focusable elements to receive focus in an order that “preserves meaning and operability”. This special browser Tab restructuring helps ensure that requirement can be met.

What browsers don’t do

We can keep this one short: the browser will not do anything apart from the behaviours listed above. Browsers will not add behaviors based on which elements you use or which role you add to them. The popover attribute is merely a first step for us to build new components.

Conclusion

The popover attribute provides a starting point for building popover-like interactions on the web. Browsers don't magically make your components accessible, that's not a thing. But there are some specific keyboard behaviours included with popover, as well as these semantics:

  • In specific cases, browsers set the aria-expanded state and/or set the aria-details relationship on the invoker.
  • Browsers apply the group role to the popover content if it doesn’t have a role of its own, so that assistive technologies can expose their boundaries.

Browser support note: at the time of writing, Safari only sets aria-expanded, not aria-details. It also doesn't add a group role fallback.


Originally posted as On popover accessibility: what the browser does and doesn’t do on Hidde's blog.

Reply via email