Skip to main content

Notes

Making Wishlist and Save-for-Later Buttons Accessible

Quietramp ·

Making Wishlist and Save-for-Later Buttons Accessible

Quick answer: the heart-shaped “add to wishlist” or “save for later” icon on a product card or product detail page is almost always a plain <div> or <span> with a click handler and a CSS fill-color swap — no accessible name, no exposed pressed/selected state, and a saved-vs-unsaved signal that’s color alone (outline heart vs. filled red heart). That’s three separate WCAG 2.1 AA failures stacked on one small control: SC 4.1.2 Name, Role, Value (Level A) for the missing name and state, and SC 1.4.1 Use of Color (Level A) for the color-only signal. Both have a specific, documented fix, and neither requires changing what the button looks like.

This article is general accessibility information, not legal advice. Meeting WCAG 2.1 AA is a technical-practice standard, not a legal determination — consult qualified counsel for legal risk questions.

Why this control fails so consistently

A wishlist heart is a toggle: it has exactly two states (saved / not saved), and clicking it flips between them. That makes it a genuine interactive control, not a decoration — but it’s almost always built to look like one first and function like a toggle second. The typical build is an icon-font glyph or inline SVG inside a clickable wrapper, with the saved state represented purely by swapping a CSS class that changes the fill from transparent to red. Visually that reads instantly. To a screen reader, that same element is either silent or announces “button” with no indication of what it does or whether it’s currently on or off.

This is a narrower problem than the one covered in our color and size variant swatches guide, which walks through the WAI-ARIA APG toggle-button pattern for swatches. That article’s fix — a fixed name (“Red”) plus aria-pressed — is right for a swatch, since a color name can’t change. A wishlist button’s label can change, which opens up a second valid fix swatches don’t need. Both are below.

1. No accessible name at all (fails SC 4.1.2 Name, Role, Value)

The most common version of this failure isn’t a wrong or unhelpful name — it’s no name at all. A bare <i class="icon-heart"></i> or an SVG with a click handler bound to its parent <div> carries no text a screen reader can read. WCAG SC 4.1.2 Name, Role, Value exists, in the Understanding doc’s own words, so that “Assistive Technologies (AT) can gather appropriate information about, activate (or set) and keep up to date on the status of user interface controls in the content.” Its own worked example names this exact category of component directly: “A page uses custom widgets — such as toggle buttons, comboboxes, or disclosure widgets — implemented using a combination of HTML and ARIA, to make sure that they programmatically convey their accessible name, their role, and their current state and value.” A wishlist heart is precisely that kind of custom toggle widget.

The baseline fix is unglamorous but non-negotiable: the element needs to be a real, keyboard-focusable <button>, and it needs a text name — either visible text or an aria-label.

<!-- Fails: no role, no name, no state -->
<div class="wishlist-icon" onclick="toggleWishlist()"></div>

<!-- Passes the accessible-name bar -->
<button type="button" aria-label="Add to wishlist">
  <svg aria-hidden="true" focusable="false"><!-- heart icon --></svg>
</button>

aria-hidden="true" on the icon itself matters here too — without it, some browser/assistive-technology combinations expose the SVG as a second, separate element, so a screen reader announces the button’s label and then an empty or redundant “graphic” right after it.

2. Exposing the saved/unsaved state — two valid patterns

A name alone only gets you halfway. The button also needs to expose whether the item is currently saved, and this is where a wishlist toggle genuinely differs from a swatch button. The WAI-ARIA APG’s Button pattern documents the toggle button explicitly, and it lays out two distinct ways to satisfy SC 4.1.2 for a two-state control like this — pick one, not both.

Pattern A: a fixed label plus aria-pressed. The APG’s own wording is specific about why this pattern requires a stable label: “it is critical the label on a toggle does not change when its state changes.” Its example is a mute button — the label stays “Mute” whether or not sound is actually muted, and only aria-pressed communicates the current state: “a screen reader would say something like ‘Mute toggle button pressed.’”

<button type="button" aria-label="Wishlist" aria-pressed="true">
  <svg aria-hidden="true" focusable="false"><!-- filled heart --></svg>
</button>

Pattern B: a label that changes with the state, and no aria-pressed at all. The APG documents this as the direct alternative to Pattern A: “if the design were to call for the button label to change from ‘Mute’ to ‘Unmute,’ the aria-pressed attribute would not be needed.” A wishlist button is a natural fit for this pattern, since “Add to wishlist” and “Remove from wishlist” are both clear, specific labels on their own — no toggle-state ARIA required.

<button type="button" aria-label="Remove from wishlist">
  <svg aria-hidden="true" focusable="false"><!-- filled heart --></svg>
</button>

Either pattern is a correct, complete fix on its own. What isn’t correct is the pattern that shows up most often in the wild: a visual fill-color change with neither aria-pressed nor a changing label — the icon looks different, but nothing in the accessible name or state model moved at all, so a screen reader user who successfully activates the button has no way to confirm it did anything.

3. Color alone signals the saved state (fails SC 1.4.1 Use of Color)

Even once the button has a name, a common remaining gap is that the only visual difference between saved and unsaved is fill color — an outlined heart versus a solid red one, with no change in shape, position, or accompanying text. WCAG SC 1.4.1 Use of Color states plainly: “Color is not used as the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element.” The Understanding doc’s own example is a form that marks required fields in red text and with an icon, specifically so that “users who cannot perceive the difference between the optional field labels and the red labels for the required fields will still be able to see the icon” — the same principle applies to a heart that’s “red” versus “not red” as its only differentiator.

This lands as a double failure on one control: a sighted user with a color vision deficiency may not reliably tell a filled heart from an outline one, and a screen-reader user gets no color information at all without a name or state attribute doing that work instead. A stroke-vs-fill shape distinction, or a brief on-screen label change (“Saved” next to the icon), gives sighted users a non-color cue while the aria-pressed or changing-label fix above handles the assistive-technology side.

Confirming the click actually worked

Some sites also show a brief toast — “Added to your wishlist” — on activation. That’s a status-message pattern, not specific to wishlist buttons, and it’s already covered in more depth in our checkout and cart accessibility guide and faceted search guide, both built on WCAG SC 4.1.3 Status Messages. Short version: if the button’s aria-pressed state or changing label already updates correctly, a screen reader will announce that change on its own — a toast is a nice-to-have, not a requirement, once the button’s own accessible state is correct.

What a scanner catches here, and what still needs a person

An automated scanner reliably flags the most literal version of this — a clickable element with no role and no accessible name at all typically surfaces as a violation. What it routinely misses: an aria-label that’s present but static while aria-pressed never actually updates on click, or a filled/outline icon swap with no underlying state change at all. Confirming those requires someone actually toggling the control with a keyboard and a screen reader and listening to what gets announced — not just checking that an ARIA attribute exists somewhere in the markup.

FAQ

Which pattern should I use — aria-pressed or a changing label? Either is correct. A changing label is often the simpler build if the design already swaps button text or a tooltip. aria-pressed with a fixed label fits better if the visible label should stay constant (just “Wishlist” or “Save”).

Do I need both aria-pressed and a changing label? No — pick one. The APG documents them as alternatives, not a pair; combining them is redundant.

Is a heart icon that fills with color, with no label at all, ever acceptable? No — an icon-only control with no accessible name fails SC 4.1.2 regardless of what the icon looks like or what color it turns. The icon can stay exactly as designed once a real <button> with a name sits underneath it.

Get your interactive controls checked by a person, not just a parser

Quietramp’s $890 audits include a manual pass through interactive controls like wishlist and save-for-later buttons — checking accessible name, exposed state, and what a real screen reader announces before and after a click, not just confirming that some markup exists. See a real sample report or check pricing — $99/month for ongoing re-checks after fixes ship.


This article is general accessibility information, not legal advice. Meeting WCAG 2.1 AA does not resolve ADA or any other legal compliance question, and nothing here should be read as a compliance guarantee. Consult qualified counsel for legal risk questions.

This article was drafted with AI assistance and reviewed by a person for accuracy before publication.

← Back to Notes