aria-pressedaria-pressed indicates the current "pressed" state of a toggle button. It supports three values, true, false, and mixed. It is intended for use on a real <button> element, or an element with role="button".
This page uses a single live example, a set of server controls, to demonstrate all three states, and to demonstrate why mixed exists in the first place: it is a computed consequence of related controls disagreeing, not a state a button starts in or that a user reaches directly.
The "All servers" button controls three individual server buttons. Its own aria-pressed state and label are not set directly, they are recalculated every time a server button changes:
aria-pressed="false", labelled "Turn all servers on"aria-pressed="true", labelled "Turn all servers off"aria-pressed="mixed", also labelled "Turn all servers off"There is no aria-live region on this page. State changes are only announced when focus lands on, or returns to, a button whose aria-pressed or label has changed since it was last focused. This is deliberate: it is the honest, default behaviour of aria-pressed without any extra announcement mechanism layered on top.
Try this:
Expected result: worth confirming directly rather than assuming, whether mixed is announced distinctly from true or false (for example, as "mixed" or "partially pressed" rather than "pressed") has historically varied across screen reader and browser combinations, and is worth checking against your standard test set.
<button id="all-servers" aria-pressed="false">
Turn all servers on
</button>
<button id="server-1" data-name="Server 1" aria-pressed="false">
Turn Server 1 on
</button>
<!-- Server 2 and Server 3 follow the same pattern -->
const allServers = document.getElementById('all-servers');
const serverButtons = Array.from(document.querySelectorAll('.server-button'));
function serverLabel(name, isPressed) {
return isPressed ? `Turn ${name} off` : `Turn ${name} on`;
}
function updateAllServers() {
const states = serverButtons.map(btn => btn.getAttribute('aria-pressed'));
const allOn = states.every(state => state === 'true');
const allOff = states.every(state => state === 'false');
if (allOff) {
allServers.setAttribute('aria-pressed', 'false');
allServers.textContent = 'Turn all servers on';
} else if (allOn) {
allServers.setAttribute('aria-pressed', 'true');
allServers.textContent = 'Turn all servers off';
} else {
// Mixed and all-on share the same label, turning off is always
// the offered action unless every server is already off.
// Colour, not text, is what distinguishes mixed from all-on here.
allServers.setAttribute('aria-pressed', 'mixed');
allServers.textContent = 'Turn all servers off';
}
}
serverButtons.forEach(btn => {
btn.addEventListener('click', () => {
const isPressed = btn.getAttribute('aria-pressed') === 'true';
const nextPressed = !isPressed;
btn.setAttribute('aria-pressed', String(nextPressed));
btn.textContent = serverLabel(btn.dataset.name, nextPressed);
updateAllServers();
});
});
allServers.addEventListener('click', () => {
const currentState = allServers.getAttribute('aria-pressed');
// Only turn everything on if every server is currently off.
// Otherwise (true or mixed), the action is always to turn everything off.
const turnOn = currentState === 'false';
serverButtons.forEach(btn => {
btn.setAttribute('aria-pressed', String(turnOn));
btn.textContent = serverLabel(btn.dataset.name, turnOn);
});
updateAllServers();
});
A few deliberate choices in this example are worth calling out rather than leaving implicit:
aria-pressed itself: a changing action label already tells the user what will happen, so pairing it with aria-pressed means the state is communicated twice, once in the visible text and once in the attribute. It is used here because it is realistic and because the underlying aria-pressed value is still needed to compute the "all servers" mixed state, not because it is the cleanest possible pattern.Note for testing: confirm the mixed state announcement, and that "mixed" and "all on" are correctly distinguished by colour when they share the same button text, with your standard browser and screen reader combinations before treating them as settled.