Disclosure Widgets

A disclosure widget is a simple control whose sole purpose is to hide or show stuff.

Native

HTML has one built in via the <details> and <summary> elements. Until recently, if you wanted to use it in modern browsers you needed to use a polyfill. In most cases it was easier and simpler to skip the polyfill and use ARIA and JavaScript to make a disclosure widget.

Today you can use either method, and it is not hard to style them to look the same. If you need to support IE11, then you have your choice between the polyfill and an ARIA disclosure widget. I have more details on detecting support and using the polyfill in an old post.

For a simple disclosure pattern, where you have a summary that leads into details, then <details>/<summary> are a good fit. Once you start to go much beyond that, things get more complicated. As I wrote in Details / Summary Are Not [insert control here], it can be tempting to extend them to do more, but it may not work the way you want for all users.

ARIA Disclosure Widget

The structure is straightforward. A <button> (or a control with role="button") acts as the control that toggles display.

Add aria-expanded with either true or false to convey to assistive technology whether the widget is expanded or not.

Add aria-controls with with the value of the id from the element whose visibility is being toggled. This is optional, and support in assistive technology is sparse, but this programmatic association may allow for easier reference via CSS or script.

Keyboard Interaction

When the disclosure control has focus either Enter or Space activates the disclosure control and toggles the visibility of the disclosure content.

WAI-ARIA Roles, States, and Properties

Focus and Reading Order

For a simple disclosure do not move focus. This allows a user who accidentally triggers the control to not lose their place and immediately trigger it again.

Ensure the disclosed content immediately follows the control in the DOM. For screen reader users this allows them to continue reading without the need to hunt for the content. If the disclosed content includes interactive controls (tab stops), this allows keyboard users to quickly move to those controls.

ARIA Authoring Practices

The disclosure pattern at the WAI-ARIA Authoring Practices 1.1 is one of the few patterns there that is complete and works well across devices and platforms. The examples are also simple and straightforward.

Simple Disclosure

This example shows the basic implementation of keyboard support and ARIA. You can also see a debug version of the simple disclosure widget

See the Pen Basic Disclosure Widget by Adrian Roselli (@aardrian) on CodePen.

The following video shows how each part of the HTML is used by the screen reader to announce the name, role, and state of the control, while also showing the values and styles change.

The embedded example as seen using JAWS 2018 and Chrome 81. I used open captions (burned into the video) instead of closed captions (which allow you to adjust text) because this is a visual aid for sighted developers and I wanted to ensure the captions did not overlap the code.

JavaScript

This JavaScript is by no means ideal, nor is it progressively enhanced. It is only meant to show that you have to change one property only — aria-expanded.

function toggleDisclosure(btnID) {
  // Get the button that triggered this
  var theButton = document.getElementById(btnID);
  // If the button is not expanded...
  if (theButton.getAttribute("aria-expanded") == "false") {
    // Now set the button to expanded
    theButton.setAttribute("aria-expanded", "true");
  // Otherwise button is not expanded...
  } else {
    // Now set the button to collapsed
    theButton.setAttribute("aria-expanded", "false");
  }
}

CSS

The CSS is even simpler. You want to either hide content or leave it be. What this means is you should toggle between display: none and allowing the default to apply. Since setting the wrong display property can remove its semantics for assistive technology, it is likely safest to not define the default state and let the native semantics of the element apply or, if using display properties for layout already, allowing those to apply.

button[aria-expanded="false"] + div {
  display: none;
}

The attribute selector ensures you are only visually toggling the display when the programmatic state has been correctly set.

The next-sibling combinator (+, formerly the adjacent sibling combinator) means that your disclosed content container must immediately follow the control in the DOM, helping to enforce the reading and focus order.

Extended Disclosure Patterns

Disclosure widgets are not restricted to just toggling narrative content. If you start to use them to mimic menu-like controls then you may need to add support for Esc or click-outside-to-close. Similarly, support for arrow keys, Home, End, Page Up, Page Down and potentially Shift + F10 may be warranted. If displaying the disclosed content overlaying other content, then z-index will be necessary.

No matter what you initially add, testing with users will be critical to creating a pattern that makes sense. Remember that once you start to modify an existing vetted pattern, that makes it no longer an existing vetted pattern.

In Link + Disclosure Widget Navigation I show how disclosure widgets can be used to create site navigation. Since disclosure widgets can contain structured and interactive content, this can become a mega-menu easily.

Using Firefox 68.0b3 and NVDA 2019.1.1. You do not hear it announced, but when on the About button and the Press Events sub-link I press Esc to hide the list.

Table with Expando Rows

My post Table with Expando Rows demonstrates how disclosure widgets can toggle the display of structured content, such as table rows.

Using JAWS 2018 and Internet Explorer 11 to navigate the table with a disclosure widget that lives in a single cell. Note that JAWS is visually highlighting the first cell in a row that has content, not the blank cells. It also does not highlight the buttons.

Accordion

An accordion is more than a few disclosure widgets or <details>/<summary> elements one after another. While an accordion is built up from a series of disclosure widgets, these widgets should be grouped and use a group label to inform screen reader users of the relationship.

Ideally they also should allow only one to be open at a time, and restrict the height of the entire collection to no taller than the viewport. The height is maintained by making the disclosed panels for each scroll instead. The accordion section of the article Designing for Progressive Disclosure gives more detail (along with other disclosure widget uses)

The following example demonstrates it, but the code is not production ready. You can also view the debug version of this accordion.

See the Pen Accordion by Adrian Roselli (@aardrian) on CodePen.

If you are interested in code that is more production-ready and progressively-enhanced, start with Scott O’Hara’s Progressively enhanced ARIA accordions. If you agree with me that the accordion should have a fixed height and scrolling panels within, then you will need to adapt Scott’s code.

Other Uses

Disclosure widgets can be modified a bit to take on other tasks. A simple example is a hamburger navigation. A more complex example could be a tool-tip replacement. An even more complex example is to create a listbox-like control that, unlike a listbox, allows the control to contain content other than items with the option role.

Hamburger-style navigation from a mobile layout. A control that looks like an expanded select box, but has plain text, two date fields, and a link. A question mark next to a piece of text, with a large box of text overlapping the content.
Here I show a hamburger navigation button, a listbox-like control, and a tool-tip-like box. I opted against full code samples since building them was delaying this post for way too long.

Each of the examples above warrants testing with real users — specifically the users of your product / service / screens. That testing should include people with disabilities, ideally whose skill levels correspond to your audience.

Closing the Widget

Each of these examples should close when the user presses Esc. This does not correspond to the native <details> / <summary> experience. However, there are many cases where closing (hiding) on Esc make sense, particularly where your control can obscure other content.

Some design implementations may warrant closing (hiding) the additional content when a user clicks or taps outside the content area, particularly when used as navigation or controls that resemble native form elements.

Managing Focus

Disclosure widgets by default do not move focus to the content area that is displayed. For your users, there may be cases where that makes sense.

In those scenarios determine if moving focus to the first interactive item is the best fit, or to the container instead. If you move focus to the container, the container must be able to accept focus (tabindex attribute) and have an accessible name and appropriate group role.

Keyboard Navigation

If you are using a visual style that looks like a <select>, then you may want to add support for arrow keys. Bear in mind, if you do that you will also want to add support for Home, End, Page Up, Page Down and potentially Shift + F10.

Remember that screen readers intercept arrow keys, so be certain to test. It is important you understand how it functions and if there are any problems for real users.

Displaying on Hover or Focus

Generally you want to avoid revealing the hidden content when a control receives focus or is hovered. Tool-tips are a valid exception, but in most other cases this will produce usability challenges.

If you pursue it, ensure that there is no nested interactive content, as a user may be unable to access it when only hovering. Unless the revealed content persists. Which means it will also need to be dismissable (see Esc support above). See Success Criterion 1.4.13: Content on Hover or Focus (AA, WCAG 2.1) for more detail.

Native versus ARIA Comparison

That was a lot. From here to the end I am just tracking screen reader announcement.

Visually the native <details>/<summary> can generally be styled to look the same as a simple disclosure. You may need different selectors since in an ARIA disclosure the control and the disclosed content are (or should be) siblings, while the <details> element is the parent to <summary>.

See the Pen Native vs ARIA Disclosure Widget by Adrian Roselli (@aardrian) on CodePen.

Following are examples of how different screen reader and browser combinations announce the two patterns. I use accName to stand in for the visible text on the controls so you do not confuse the name with what the screen reader announces.

JAWS 2019 / Chrome 81

As far as JAWS is concerned, these are the same control. Under the hood in Chrome, the <summary> has a role of DisclosureTriangle, but it is either mapped to the button role or just announced as if it is. Note that if you use a control other than a <button> for the ARIA pattern (why?), but use the button role, you had better add support for the Space bar since that is what JAWS tells users to press.

Native
accName button [collapsed|expanded]. To activate press Space bar.
ARIA
accName button [collapsed|expanded]. To activate press Space bar.

NVDA 2019 / Firefox 76

NVDA also considers them to be the same, at least for the user. Under the hood in Firefox, the <summary> has the summary role, and the <button> has the pushbutton role.

Native
accName [collapsed|expanded].
ARIA
accName [collapsed|expanded].

macOS 10.15.2 / VoiceOver / Safari

VoiceOver considers them functionally the same, but since each is a different control type it says so.

Native
accName [collapsed|expanded] summary.
ARIA
accName [collapsed|expanded] button.

Android 10 / Chrome 81 / TalkBack

TalkBack with Chrome will announce <details>/<summary> as disclosure triangle, while the ARIA pattern is announced as button (because I used a <button>).

Native
[collapsed|expanded] accName disclosure triangle
ARIA
[collapsed|expanded] accName button

Android 10 / Firefox 68 / TalkBack

TalkBack with Firefox will not announce <details>/<summary> when navigating by controls; it gets skipped completely. If <summary> has a child node (such as an SVG), it becomes another tab-stop. The state of neither is conveyed.

Native
accName
ARIA
accName button. Double-tap to activate.

iPadOS 13.4 / VoiceOver / Safari

VoiceOver does not expose <summary> as interactive when the <details> is expanded. This may not be a big deal unless you have verbose content or controls that would annoy VoiceOver users to have to navigate through or otherwise jump past (if not, why did you use it?).

Native, collapsed
accName collapsed. Double-tap to expand.
Native, expanded
accName.
ARIA, collapsed
accName button collapsed. Double-tap to expand.
ARIA, expanded
accName button expanded. Double-tap to expand.

Wrap-up

A disclosure widget is a simple control whose sole purpose is to hide or show stuff. Using the native control should be limited to simple content. The ARIA pattern can be extended to behave as richer controls. No matter what, always test.

One Comment

Reply

Great topic. One remaining major “gotcha” with the native details/summary widget is the lack of support for headings in the summary element in JAWS. Headings may not be necessary in every implementation of disclosure widgets, but it’s a pretty important pattern. There does not appear to be a complete consensus over how the summary element should be treated by user agents (is it a button? Should headings be allowed within it, even though they are not valid within buttons? etc.)

Peter Weil; . Permalink

Leave a Comment or Response

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>