Search topics
Toggle buttons that don't say whether they are pressed
A Favorite button fills its heart when pressed, but the pressed state is only a CSS class, so assistive technology hears the same button either way.
<button class="fav is-saved" aria-label="Favorite"> The problem
Screen reader users can press the Favorite button but can't tell whether the item is saved, or whether pressing it did anything.
A product page shows a canvas backpack with a heart button beside its name. Pressing the heart fills it in, and the backpack is added to the shopper's favorites. A screen reader user tabs to the heart and hears "Favorite, button". They press Enter. The heart fills in, and they hear nothing new. When they come back to the button later, it still says "Favorite, button". They can't tell whether the backpack is saved, so they either press again and risk removing it, or go and look for it in their favorites list.
The same failure turns up in any button that stays down once pressed: Bold and Italic in an editor, Mute in a video player, Follow on a profile, and filter chips such as "In stock" or "On sale".
Success criterion 4.1.2 requires that the state of a user interface component can be determined by software, and that assistive technology is told when it changes. A toggle button has two states, pressed and not pressed. Here the state is only the is-saved class that fills the heart. The button has a name and a role, but nothing in the accessibility tree says pressed, so the change is drawn but never announced.
Favorite and filter toggles often sit in long lists of products or results, so a person checking what they saved has to check each heart in turn. When the state isn't exposed, every one of them is a guess.
3 of 10 groups affected
- No vision (affected)
- Low vision (affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (not affected)
- Reach, strength (not affected)
- Cognitive (affected)
- Seizures (not affected)
- Without vision Screen reader users hear "Favorite, button" whether the item is saved or not, so they can't check what they saved or confirm that pressing worked.
- Limited vision People zoomed in or using a magnifier may not see a small heart fill in, and get no spoken state to fall back on when they also use a screen reader.
- Limited language, cognitive and learning abilities Without a clear state, people who need confirmation may press again to be sure, and unsave the item they meant to keep.
The broken button passes every automated check: it has a role and a name, and nothing marks it as a toggle. No scanner can tell that the class means pressed. Code assistants often get close but contradict themselves. They add aria-pressed and also change the name to "Remove from favorites", so it is announced as "Remove from favorites, toggle button, pressed". Or they put aria-checked or aria-selected on a button, where neither belongs. Press the button and listen to what changes.
Try it
Press Tab to reach the heart, then Enter or Space to press it.
The panel shows what a screen reader announces. Can you tell whether the backpack is saved?
Broken Can you fix this?
Screen reader says:
(Tab to the heart)A recreated demo. The broken version is intentionally inaccessible.
The fix
Expose the pressed state with aria-pressed, a checkbox, or a name that says what pressing will do.
- 1Expose the state in code: aria-pressed, a checkbox, or a name that says the next action.
- 2Let the state or the name carry it, never both: no aria-pressed on a name that changes.
- 3Draw the pressed look from the value that is announced, and not by colour alone.
<button type="button" class="fav" aria-label="Favorite"> <svg aria-hidden="true" viewBox="0 0 24 24"><path d="…"/></svg></button>A toggle button with aria-pressed
<button type="button" class="fav" aria-pressed="false" aria-label="Favorite"> <svg aria-hidden="true" viewBox="0 0 24 24"><path d="…"/></svg></button>Why this fix: Style from the state, not a class
The button can be reached with Tab and pressed with Enter and Space, but its state is only the is-saved class. A screen reader announces "Favorite, button" whether the backpack is saved or not, and says nothing when it changes.
aria-pressed makes the button a toggle button, announced as "Favorite, toggle button, not pressed" and then "pressed" when it changes. The name stays "Favorite" and the state does the rest. The heart is filled from the attribute, not from a class, so the picture and the announcement come from one value and can't drift apart.
All three pass 4.1.2, and the right one depends on what the toggle is and what is already there. When you are building a toggle that looks and acts like a button, such as Favorite or Bold, use aria-pressed: it keeps one name, is announced on focus and the moment it changes, and costs one attribute. HTML has no toggle button element, so this is not ARIA standing in for a native one. When the toggle is really a choice in a form, such as filters sent together, or the markup is already a checkbox, keep the checkbox: the browser holds the state and it works without script, though it is announced as a checkbox and does not respond to Enter. When the design already changes the label to the next action, as Mute and Unmute do, keep the changing name rather than adding aria-pressed on top, and test that the new name is announced, or confirm the result in a status message.
Keep the name the same in both states when you use aria-pressed or a checkbox. "Favorite" with a state of pressed is clear. "Remove from favorites, pressed" says two different things at once.
The same choice applies when the label is visible text, as on a Follow button.
- With
aria-pressed, keep the word "Follow" and draw the pressed look from the attribute. - With a changing label, make the new word the next action, "Unfollow", not "Following", and leave
aria-pressedoff. - A label that changes to describe where things stand, such as "Following", tells sighted users the state but reads to everyone else as an action.
Draw the pressed state with more than colour. A heart that changes from outline to filled is a change of shape, which forced colors keeps if the fill uses currentColor; a heart that only turns from grey to red disappears there and for people who can't tell the two apart.
Saving a favorite usually means a request to the server. Change the state when the person presses, and if the save fails, set it back and say so in a status message, rather than leaving the heart filled for an item that wasn't saved.
Component libraries often put the pressed state on a wrapper or an icon. Check the element that takes focus, because that is the one whose state is read. When the toggle is a settings row with an on and off position rather than a button, see toggle switches instead.
Fixing this with an AI coding assistant? Get this guide as Markdown
Verify the fix
5 checks.
Test with: Keyboard, Screen reader, Forced colors, Mouse
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared button component changes.
Limits
This is a starting pattern, not a guarantee for every application. Screen readers word toggle buttons differently, and whether a changed name is announced depends on the screen reader and browser. The CSS is a sketch; check the focus indicator and the contrast of the heart against your own palette. Test the complete journey, from saving an item to finding it in the favorites list, with your target assistive technology.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
Keep this fixed as your code changes.
Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.