Disclosure
A disclosure is a widget that allows content to be shown or hidden.
A disclosure widget can be implemented using the native HTML
details
and
summary
elements, which are
supported by all modern browsers. In that case the browser will handle the disclosure's functionality and
ensure that the disclosure's state is conveyed to users of assistive technologies. In the remainder of this
entry, this kind of disclosure will be referred to as a native disclosure.
Non-native disclosures are implemented using two elements: a button that controls the visibility of the content and an element that contains the content, referred to as the panel. In the remainder of this entry, this kind of disclosure will be referred to as an ARIA disclosure since it utilizes ARIA roles and attributes to convey the disclosure's semantics to users of assistive technologies.
Unless explicitly specified, the sections of this entry apply only to ARIA disclosures.
Role
An ARIA disclosure widget's control must have a role of button
. The best way to achieve this is to use the
semantic HTML button
element. If you are using a non-button element, you must add the role="button"
attribute to the element.
1<!-- Better to use the button element -->2↖ <button aria-expanded="false">What is the meaning of life?</button>34<!-- If not using the button element, add `role="button"` -->5↘ <div role="button" aria-expanded="false">What is the meaning of life?</div>
Button name
A disclosure's control button must have an accessible name. The accessible name is the text that is read by screen readers to describe the element. The accessible name should indicate what content will be shown or hidden when the button is activated.
In native disclosures, the summary
element serves as the button. The HTML standard does not
require the details
element to contain a summary
element. However, if the summary
element is not
present, the button that controls the disclosure will have no accessible name. Therefore, the summary
element should always be present as a direct child of the details
element and should always have an
accessible name.
1<!-- The summary element serves as the button -->2<details>3 <summary>What is the meaning of life?</summary>4 425</details>
Buttons support name from content, so its accessible name could be given from a direct text node child, an image with an alternative text, or even multiple children elements with an accessible name. This applies for both native and ARIA disclosures.
Othewise, the accessible name can be provided in a couple of ways:
1<!-- The img tag's alt attribute constitutes the button's accessible name -->2<button aria-expanded="false">3 <img src="./disclosure-collapsed.png" alt="What is the meaning of life?" />4</button>56<!-- Button gets its accessible name from the aria-label attribute -->7<button aria-expanded="false" aria-label="What is the meaning of life?">8 <img src="./disclosure-collapsed.png" aria-hidden="true" />9</button>
Focus sequence
The disclosure button must be focusable unless it is disabled. This is important for keyboard users who need to be able to navigate the button.
Semantic HTML buttons are always focusable when not disabled. Non-semantic buttons
can be made focusable by adding the tabindex="0"
attribute to the button element.
1<div role="button" aria-expanded="false" aria-label="What is the meaning of life?" tabindex="0" />
Disabled buttons
A semantic HTML button can be disabled by adding the disabled
attribute to the button element.
A disabled button is not focusable.
Non-semantic buttons can be disabled by adding the aria-disabled="true"
attribute to the button element.
In that case the developer may choose to make the button focusable or not.
It should not be possible to operate the disclosure when disabled, see Toggle activation test.
The aria-disabled
attribute should not be used on a semantic HTML button.
1<!-- Disabled non-semantic HTML button -->2<div role="button" aria-expanded="false" aria-label="What is the meaning of life?" aria-disabled="true" />34<!-- Disabled non-semantic HTML button that is focusable -->5<div role="button" aria-expanded="false" aria-label="What is the meaning of life?" aria-disabled="true" tabindex="0" />67<!-- Semantic HTML button should not use aria-disabled -->8✓ <button aria-expanded="false" aria-label="What is the meaning of life?" disabled />9✗ <button aria-expanded="false" aria-label="What is the meaning of life?" aria-disabled="true" />
ARIA owns
If the disclosure should contain elements that cannot be implemented as descendants of the disclosure,
the aria-owns
attribute can be used to indicate that the disclosure owns the elements.
However, since the aria-owns
attribute is not supported by some assistive technologies,
it is best practice to avoid it. If it must be used, the aria-owns
attribute should be set
on the disclosure and should point to the IDs of the owned elements.
Panel
ARIA controls
The disclosure button must have an aria-controls
attribute that references the id
of element that contains
the content that will be shown or hidden when the button is activated (the panel). It is acceptable for the aria-controls
attribute only to be present when the button is in the expanded state, although the best practice is to always
include the attribute.
Accessibility label
Similarly the best practice is for the panel to have an aria-labelledby
attribute that references the id
of
disclosure button. This ensures that the panel's accessible name is the same as the button's accessible name.
1<button2 id="button-42"3 aria-expanded="false"4 aria-controls="disclosure-panel-42"5>6 What is the meaning of life?7</button>8<div id="disclosure-panel-42" class="disclosure-panel-hidden" aria-labelledby="button-42">42</div>
Toggle activation
Conveying the state of the disclosure
The state of the disclosure must be conveyed to screen reader users.
This must be done by adding the aria-expanded
attribute to the button element and
updating the attribute when the button is activated and the accompanying content is shown or hidden.
The aria-expanded
attribute must be set to either true
or false
.
When the aria-expanded
attribute is set to true
, the disclosure panel must be visible and
perceivable to screen reader users. When the aria-expanded
attribute is set to false
,
the disclosure panel should not be visible and should be hidden from screen reader users.
1function toggleDisclosure(buttonElement) {2 const isExpanded = buttonElement.getAttribute('aria-expanded') === 'true';3 const disclosurePanel = document.getElementById(buttonElement.getAttribute('aria-controls'));4 if (isExpanded) {5 buttonElement.setAttribute('aria-expanded', 'false');6 disclosurePanel.classList.add('disclosure-panel-hidden');7 disclosurePanel.classList.remove('disclosure-panel-visible');8 } else {9 buttonElement.setAttribute('aria-expanded', 'true');10 disclosurePanel.classList.add('disclosure-panel-visible');11 disclosurePanel.classList.remove('disclosure-panel-hidden');12 }13}
Keyboard activation
A disclosure button must be able to be activated using the keyboard.
This is important for keyboard users who need to be able to activate the disclosure.
This can be achieved by adding a keyup
event listener to the button element.
The event listener should check for the Enter
and Space
keys.
1const disclosureButtons = document.querySelectorAll('button[aria-expanded][aria-controls]');2disclosureButtons.forEach((buttonElement) => {3 buttonElement.addEventListener('click', () => toggleButton(buttonElement));4 buttonElement.addEventListener('keyup', (event) => {5 if (event.key === 'Enter' || event.key === ' ') {6 event.preventDefault();7 toggleDisclosure(buttonElement);8 }9 });10});
Toggle activation test
The toggle activation test checks that the disclosure button can be activated by both mouse and keyboard.
It also tests that when activated, either the button name or the aria-expanded
attribute is updated
to reflect the new state of the Button.
The test uses the aria-expanded
attribute to verify that the state of the button
changes for each activation method.
It runs three times to test activation using a mouse click, the Enter
key and the Space
key.
For disclosure buttons that are not implemented using a native HTML button, the test will check
that for disabled buttons (aria-disabled="true"
), their state does not changed when
clicked or activated using the keyboard.
Escape key
When part of some patterns (e.g. Site navigation), the disclosure panel should
be closed when the Escape
key is pressed. This can be achieved by adding a keyup
event listener to
the button element (and the panel element if it is focusable or contains focusable elements). Native
disclosures do not automatically close when the Escape
key is pressed, so this requirement applies
to both native and ARIA disclosures.
1const disclosureButtons = document.querySelectorAll('button[aria-expanded][aria-controls]');2disclosureButtons.forEach((buttonElement) => {3 buttonElement.addEventListener('click', () => toggleButton(buttonElement));4 buttonElement.addEventListener('keyup', (event) => {5 if (event.key === 'Enter' || event.key === ' ') {6 toggleDisclosure(buttonElement);7 }8 if (event.key === 'Escape' && buttonElement.getAttribute('aria-expanded') === 'true') {9 event.preventDefault();10 toggleDisclosure(buttonElement);11 }12 });13 const panelElement = document.getElementById(buttonElement.getAttribute('aria-controls'));14 if (panelElement) {15 panelElement.addEventListener('keyup', (event) => {16 if (event.key === 'Escape' && buttonElement.getAttribute('aria-expanded') === 'true') {17 event.preventDefault();18 toggleDisclosure(buttonElement);19 }20 });21 }22});