Testing aria-pressed

aria-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.

Example: servers

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:

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:

  1. Tab to "All servers". It should announce as "Turn all servers on," not pressed.
  2. Tab to "Turn Server 2 on" and activate it. It should now read "Turn Server 2 off," pressed.
  3. Shift-tab back to "All servers". It should now read "Turn all servers off," mixed, not "Turn all servers on."
  4. Turn the remaining two servers on individually. "All servers" should now read "Turn all servers off," pressed, once all three agree.
  5. Activate "All servers" directly from an off or mixed state. All three servers should turn on and "All servers" should read "Turn all servers off," pressed.
  6. Activate "All servers" again while it is pressed. All three servers should turn off and "All servers" should return to "Turn all servers on," not pressed.

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.

HTML markup
<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 -->
JavaScript
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();
});

Design notes

A few deliberate choices in this example are worth calling out rather than leaving implicit:

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.