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>
3
4<!-- 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 42
5</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>
5
6<!-- 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" />
3
4<!-- 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" />
6
7<!-- 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" />

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<button
2 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});

Summary of Tests

NameConformance LevelWCAG Success Criteria
RoleA4.1.2 Name, Role, Value
Button nameA4.1.2 Name, Role, Value
Focus sequenceA2.1.1 Keyboard, 2.4.3 Focus Order
ARIA controlsA1.3.1 Info and Relationships
ARIA expandedA1.3.1 Info and Relationships
Accessibility labelA4.1.2 Name, Role, Value, 1.3.1 Info and Relationships
Toggle activationA2.1.1 Keyboard, 4.1.2 Name, Role, Value, 1.3.1 Info and Relationships
Escape keyA2.1.1 Keyboard