Toggle Button

A toggle button is a button that can be toggled between two or three states. A toggle button that supports three states is called a tri-state toggle button. In the case of a two-state toggle button, the button can be toggled between an on (pressed) and off (not pressed) state. In that case the state of the button may be conveyed using the aria-pressed attribute or by changing the button label (e.g. "Mute" and "Unmute"). In the case of a tri-state toggle button, the button label must be used to convey the state.

A toggle button may be implemented using a semantic HTML button element (recommended) or a non-button element with the role="button" attribute.

Role

A toggle button widget 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 class="mute">Mute</button>
3
4<!-- If not using the button element, add `role="button"` -->
5<div class="mute" role="button">Mute</div>

Button name

A toggle must have an accessible name. The accessible name is the text that is read by screen readers to describe the element. There are many ways to provide an accessible name for a toggle button. The accessible name should convey the functionality of the button. If the aria-pressed attribute is used to convey the state of the button, the accessible name must not include the state of the button, see toggle activation. If the aria-pressed attribute is not used, the accessible name should include the state of the button, for instance the button label could change from Mute to Unmute when muted.

1<!-- The button text constitutes the accessible name -->
2<button aria-presssed="false" class="mute">
3 <span class="visually-hidden">Mute</span>
4</button>
5
6<!-- The button gets its accessible name from the aria-label attribute,
7 the name will also change to convey the state of the button so
8 aria-pressed is not set. -->
9<button aria-label="Mute" />
10
11<!-- Button gets its accessible name from the aria-labelledby attribute -->
12<span id="mute-label">Mute</span>
13<button aria-pressed="false" aria-labelledby="mute-label">
14 <span class="on" aria-hidden="true">On</span>
15 <span class="off" aria-hidden="true">Off</span>
16</button>

Focus sequence

A 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-pressed="false" aria-label="Mute" 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 toggle the state of the button 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-pressed="false" aria-label="Mute" aria-disabled="true" />
3
4<!-- Disabled non-semantic HTML button that is focusable -->
5<div role="button" aria-pressed="false" aria-label="Mute" aria-disabled="true" tabindex="0" />
6
7<!-- Semantic HTML button should not use aria-disabled -->
8<button aria-pressed="false" aria-label="Mute" disabled />
9<button aria-pressed="false" aria-label="Mute" aria-disabled="true" />

Forbidden interactive children

A button must not contain any interactive children such as links or buttons. Such children would be inaccessible to screen reader users since they will be excluded from the accessibility tree.

Toggle activation

Conveying the state of the button

A toggle button can support up to three states. Activating the button toggles through the states. The state of the button must be conveyed to screen reader users. In the case of a button with two states, this can be done by adding the aria-pressed attribute to the button element and updating the attribute when the button is activated. The aria-pressed attribute must be set to either true or false.

1<!-- Toggle is initially Off -->
2<div
3 id="mute-button"
4 role="button"
5 tabindex="0"
6 aria-label="Mute"
7 aria-pressed="false"
8 class="off"
9/>
1function toggleButton(buttonElement) {
2 const isDisabled = buttonElement.getAttribute('aria-disabled') === 'true';
3 if (isDisabled) {
4 // Do nothing if button is disabled
5 return;
6 }
7 const currentState = buttonElement.getAttribute('aria-pressed');
8 if currentState == 'false' {
9 buttonElement.classList.add('on');
10 buttonElement.classList.remove('off');
11 buttonElement.setAttribute('aria-pressed', 'true');
12 } else {
13 buttonElement.classList.add('off');
14 buttonElement.classList.remove('on');
15 buttonElement.setAttribute('aria-pressed', 'false');
16 }
17}

In some cases it may be more appropriate to modify the button label to convey the state of the button. One such case is when the button has more than two states but even for a buttons with only two states, one may decide to modify the label due to design considerations. In any case where the label conveys the state of the button and is modified on activation, the aria-pressed attribute should not be used.

1<!-- Toggle label conveys its initial state -->
2<div
3 id="mute-button"
4 role="button"
5 tabindex="0"
6 aria-label="Mute"
7 class="off"
8/>
1function toggleMuteButton(buttonElement) {
2 const isDisabled = buttonElement.getAttribute('aria-disabled') === 'true';
3 if (isDisabled) {
4 // Do nothing if button is disabled
5 return;
6 }
7 const currentLabel = buttonElement.getAttribute('aria-label');
8 if currentLabel == 'Mute' {
9 buttonElement.classList.add('on');
10 buttonElement.classList.remove('off');
11 buttonElement.setAttribute('aria-label', 'Unmute');
12 } else {
13 buttonElement.classList.add('off');
14 buttonElement.classList.remove('on');
15 buttonElement.setAttribute('aria-label', 'Mute');
16 }
17}

Keyboard activation

A toggle button must be able to be activated using the keyboard. This is important for keyboard users who need to be able to activate the toggle. 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 buttonElement = document.getElementById("mute-button");
2buttonElement.addEventListener("click", () => toggleButton(buttonElement));
3buttonElement.addEventListener("keyup", (event) => {
4 if (event.key === "Enter" || event.key === " ") {
5 toggleButton(buttonElement);
6 }
7});

Toggle activation test

The toggle activation test checks that the button can be activated by both mouse and keyboard. It also tests that when activated, either the button name or the aria-pressed attribute is updated to reflect the new state of the Button. The test verifies that the button name and aria-pressed attribute are not both changed when the button is toggled. Furthermore it checks that when using aria-presseed, the button only supports two states whereas when using the button name, there are no more than three states. The test uses the button name or aria-pressed 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 toggle 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.

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 pressedA1.3.1 Info and Relationships
Forbidden interactive childrenA4.1.2 Name, Role, Value
Toggle activationA2.1.1 Keyboard, 4.1.2 Name, Role, Value