This post contains information that was accurate at the time of its initial publication. However, there have been many significant changes since then, such as the WHATWG HTML spec becoming the sole HTML specification, Edge adopting the Chromium browser engine, etc.

This post has been modified to remove and update some of its information, but there remain many references to support specific to the original publication date.

Instead, check out The details and summary elements, again for a more recent rundown of these elements.

The details and summary elements are two of HTML’s interactive elements and together are the elements that create a native disclosure widget. A “disclosure widget” being a UI component that allows for the showing / hiding (toggling the display) of content associated with the disclosure trigger (button).

General Support

Outside of Internet Explorer (which will never support the elements) and pre-Chromium Edge, browser implementation of details and summary is pretty great!

The expected markup pattern for details and summary is as following:

<details>
  <summary>
    <!-- Label for the disclosure widget -->
  </summary>
  <div> <!-- optional wrapper (for styling) -->
    <!-- collapsible content goes here -->
  </div>
</details>

With or without the optional nested div (which is only there for a wrapper to style), in its collapsed state the only visible element would be the summary and its contents.

Adding the open attribute to the details element will expand the nested content by default.

It’s worth noting that a summary element does not necessarily mean it must contain a “summary” of the other children of the details element. The HTML specification defines the element as representing “a summary, caption, or legend”, but you should treat the summary like you would any disclosure widget trigger or toggle button. Provide it an appropriate accessible name to indicate the content it will reveal, and you’ll be good to go!

If you want to apply some custom styles to the elements, the trickiest part is working with the native triangle marker that visually conveys the expanded or collapsed state.

For non-Blink or Webkit based browsers (Firefox), you can style the marker using the list-style-image property.

summary {
  list-style-image: url('');
}

Chromium, and Webkit based browsers can style the marker with the following selector.

summary::-webkit-details-marker {
  /* ... */
}

If you need to support IE11, or simply want to roll your own disclosure widget, the following markup would do the trick:

<button type="button" aria-expanded="true/false" aria-controls="id_ref">
  <!-- Label for the disclosure widget -->
</button>
<div id="id_ref">
  <!-- collapsible content goes here -->
</div>

JavaScript would be needed to toggle the state of the aria-expanded value of the button. By following this basic markup structure, you could use CSS to toggle the visibility of the collapsible content.

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

With the above ruleset, the adjacent div to the button would be hidden while the button is in the collapsed state. When toggled to aria-expanded="true" the expanded state of the pattern would be conveyed, and since the selector no longer matched the state, the div would be reset to its inherent display: block;, revealing the previously hidden content.

That’s about as straight forward as it gets. More complicated markup patterns would likely necessitate revised functionality and CSS, but I’ve already written a bit about that in my Accessible Accordions post from 2017.

Screen Reader Support

Generally screen readers do well with standard usage of the details and summary elements. There are some variations in announcements depending on the screen reader and browser, but more importantly there are a few a bugs here and there to be aware of.

Depending on how you’re using the disclosure widget, at a high level it should be discoverable by form control or button screen reader quick keys. The summary should be focusable by the tab key. If headings are used, they should be discoverable by screen readers.

The test case for the following results has been posted to CodePen.

JAWS 2018 & 2019 * + Firefox 63 (Nightly) & 64.0.2 and Chrome 68 & 71

JAWS with Firefox and Chrome will announce a native summary as its accessible name (the text of the summary), and the state of the disclosure widget when focused by virtual cursor or via the Tab key. The summary elements can also be navigated to by use of the B and F keys, like standard button elements.

When activated, JAWS + Firefox will announce the key used to toggle the disclosure widget (Space or Enter), the accessible name, role and updated state. JAWS + Chrome will announce the key pressed, and the updated state, but will not re-announce the accessible name and role.

JAWS with Firefox and Chrome will not announce, or allow users to navigate to, headings within a summary, even though using a heading within a summary is a valid content model. (a bug for this has been filed on the VFO standards support on GitHub).

There will be no change in announcements if the summary has a role="button" added to it (see VoiceOver and Safari on iOS).

VoiceOver and Safari on iOS 11.4.1 & 12.1.2

VoiceOver and Safari will announce a native summary as its accessible name, and the state of the disclosure widget, but offer no additional role announcement. The summary is not discoverable when navigating by form controls (for example, buttons) with the VoiceOver router.

VoiceOver will announce and allow navigation to a child heading of a summary element.

A role="button" can be added to the summary element to expose it as a button like other screen readers, however if a role="button" is added to the summary, then a heading will no longer be discoverable within the “button”.

Additionally, while testing I found that VoiceOver on iOS 11.4.1 will continue to announce a disclosure widget as collapsed, regardless of state, if there is no <meta name="viewport"> set in the document. This issue no longer occurs in in iOS 12.1.2.

VoiceOver and Safari 11.1.2 & 12.0.2 on macOS 10.13.6 & 10.14.2

VoiceOver and Safari will announce a native summary as its accessible name, current state, and “summary” as the role. The summary element is not discoverable if navigating by form controls.

If a heading is nested within a summary it will be announced and discoverable by VoiceOver.

If using role="button" on a summary element, the element will be announced as a “button” but the state of the disclosure widget will not be announced. If a heading is nested within a summary role="button" it will be ignored by VoiceOver.

NVDA 2018.2.1 & 2018.4.1 + Firefox 63 (Nightly) & 64.0.2

When using the Tab key, or virtual cursor to focus the summary element, NVDA will announce the accessible name (text of the summary), the current state of the widget (expanded or collapsed), and NVDA will announce the summary as a “button”.

Hitting Enter or Space will toggle the state of the widget, but no state change will be announced by NVDA with Firefox.

If “report role when mouse enters object” is turned on, NVDA will announce “button” and then the accessible name of the summary, when mouse hovering over the element. If a heading is within the summary, NVDA will announce “heading” instead, and will only announce “button” on the edges of the summary element (for example, if padding was set to the summary to push in the heading from its edges).

NVDA will expose and announce headings when used within summary elements.

NVDA 2018.2.1 & 2018.4.1 + Chrome (68 & 71)

NVDA will announce a summary in the same manner as noted with Firefox. However when togging the state of the widget, Chrome will announce the state change as “expanded” or “collapsed”, where Firefox did not, with one exception.

If NVDA’s “report role when mouse enters object” setting is checked, NVDA will announce “text” instead of “button”, when hovering over the element with a mouse.

NVDA will expose and announce headings when used within summary elements, however NVDA will no longer appropriately announce a state change when the summary is toggled (issue filed with NVDA).

Similarly, no state change will be announced with NVDA + Chrome if the summary has a role="button" to compensate for mobile VoiceOver not announcing a role for the summary element.

TalkBack (Android Accessibility Suite 6.2 & 7.2) + Android Chrome

TalkBack will announce a disclosure widget as “Collapsed, accessible name, disclosure triangle. Actions ‘expand’ double tap to activate.”

If the disclosure is expanded, “Expanded” will be announced as the state, and “Actions ‘collapse’” will be announced after TalkBack oddly (and uniquely) announces the existence of the default triangle marker.

Toggling the state of a disclosure widget will produce a standard sound effect and slight vibration, but no announcement of a state change will be made. Navigating away and back to the disclosure widget will have the new state declared as part of the announcement.

The summary element is discoverable when navigating by “controls” with TalkBack, but TalkBack ignores headings nested within the summary element.

If using summary role="button", TalkBack will modify it’s announcement of the disclosure widget to “State, accessible name, button.” Toggling the element will also not announce the changed state with TalkBack 6.2, but navigating away and back to the “button” will declare the new state in its announcement. The toggled state will be announced with TalkBack 7.2.

Wrapping up

Regardless of the few screen reader inconsistencies, and lack of support for legacy browsers, details and summary are well suited for creating native disclosure widgets.

For legacy browsers, you can use a polyfill with your own CSS to modify the elements into a custom ARIA implementation:

<details>
  <summary aria-expanded="true/false" tabindex="0" role="button">
    My trigger
  </summary>
  <div>
    <!-- content goes here -->
  </div>
</details>

Or create a custom markup pattern like one of the following snippets…

<button aria-expanded="true/false" aria-controls="id_ref">
  My trigger
</button>
<div id="id_ref">
  <!-- content goes here -->
</div>

or

<h#>
  <button aria-expanded="true/false" aria-controls="id_ref">
    My trigger
  </button>
</h#>
<div id="id_ref">
  <!-- content goes here -->
</div>

…add in some light JavaScript to toggle the aria-expanded, and maybe toggle a class for the expansion panel, and you’ll have a suitable ARIA stand-in while details and summary get their last few kinks worked out.

As mentioned, there are issues filed for the Chrome + NVDA bug and JAWS concerning the use of headings within summary.

Additional Reading

For some more background on the details and summary elements, and the concept of disclosure widgets and accordions, check out the following: