Making Wishlists Accessible: Toggle Button Pattern and Status Announcements
AI generated
A11Y
WCAG
Accessibility · Wishlist · Toggle Pattern
Accessible Wishlists
Toggle button pattern with aria-pressed and reliable status announcements for screen readers

The wishlist button is one of the most unassuming yet most frequently mismarked interaction elements in an online store: a heart icon flips its state on click without any visible change to the surrounding markup, and that silent status change is invisible to screen reader users without extra ARIA work. Applying the toggle button pattern with aria-pressed consistently, and actively announcing status changes, turns a purely visual switch into a traceable action, including while managing the wishlist itself.

9 min read aria-pressed Status announcement for toggle actions

1. Why the wishlist button is its own status change problem

Unlike plain form validation or a classic add to cart button, a wishlist button triggers no navigation and no visible new form field, it simply flips a boolean state: saved or not saved. That very simplicity makes it tricky, because development teams often treat it like an ordinary button with no special state, even though it is technically distinct from a plain action button.

A wishlist button is not a link, not a form submit, and not a simple action button, it is what is called a toggle button: a button that switches between exactly two states and keeps displaying that state until clicked again. The ARIA specification defines a dedicated attribute for this interaction type, one that is surprisingly often missing or misused in practice.

2. The toggle button pattern: aria-pressed instead of aria-checked

For a wishlist button, aria-pressed is the correct attribute, not aria-checked. aria-checked belongs to radio buttons, checkboxes and menu items with checkbox behavior and implies group membership or a form like selection. aria-pressed, on the other hand, is built exactly for the case where a single, standalone button switches between a pressed and an unpressed state, exactly the behavior of a wishlist heart.

It matters that aria-pressed is set on the button element itself and toggled between "true" and "false" via JavaScript on every click. If the attribute is missing entirely, the screen reader reports only a button with no state information at all, and the user cannot tell whether an item is already on the wishlist without checking the page visually.


<button
  type="button"
  class="wishlist-toggle"
  aria-pressed="false"
  data-product-id="1234"
  onclick="toggleWishlist(this)">
  <svg class="wishlist-toggle__icon" aria-hidden="true"><!-- heart icon --></svg>
  <span class="wishlist-toggle__label">Add to wishlist</span>
</button>

3. Implementation: a heart icon button with switching label and state

A purely visual icon that changes from an outlined to a filled heart communicates the new state only to sighted users. To keep the same switch textually understandable, the button's label should be updated alongside aria-pressed: after the click, "Add to wishlist" becomes "Remove from wishlist", not just the visual state of the icon.

This combination of aria-pressed and a switching text is doubly redundant, and precisely because of that it is robust: some screen reader modes prefer to read out the label, others read the state information from aria-pressed. If only one of the two is maintained, the announcement works incompletely in certain screen reader configurations.


function toggleWishlist(button) {
  const pressed = button.getAttribute('aria-pressed') === 'true';
  const nextState = !pressed;

  button.setAttribute('aria-pressed', String(nextState));
  button.querySelector('.wishlist-toggle__label').textContent = nextState
    ? 'Remove from wishlist'
    : 'Add to wishlist';

  syncWishlist(button.dataset.productId, nextState);
}

4. Actively announcing status changes for screen readers

In many screen reader combinations, a change to aria-pressed gets read out the next time the button is focused, but not automatically at the moment the click happens, if focus stays on the button. For a reliable, immediate response, an additional discreet aria-live="polite" region is recommended, briefly confirming that the item was added to or removed from the wishlist.

This live region should stay visually unobtrusive, for example as an sr-only element, and replace its text on every status change rather than appending to it, so screen readers do not repeatedly read out old messages again. A double announcement, once through aria-pressed and once through the live region, is not a contradiction here, it increases reliability across different assistive technologies.


<div id="wishlist-status" class="sr-only" aria-live="polite"></div>

<script>
function syncWishlist(productId, added) {
  document.getElementById('wishlist-status').textContent = added
    ? 'Item was added to the wishlist.'
    : 'Item was removed from the wishlist.';
}
</script>

5. Icon only buttons: why aria-label and contrast matter for the wishlist icon

In product listings, the wishlist button is often shown as an icon only, without visible text, for space reasons. In that case, a switching aria-pressed alone is not enough, because a button without visible text also has no accessible name. An aria-label that also updates per state then takes over the role of the label from the previous example.

Visually, a filled heart often differs from an outlined one only by a thin contour line, which is hard to recognize with low contrast or visual impairments. An additional, even subtle visual signal such as a color change or a small badge improves recognizability for users who can see but struggle with fine shape differences.


<button
  type="button"
  class="wishlist-icon-button"
  aria-pressed="true"
  aria-label="Remove from wishlist"
  data-product-id="1234">
  <svg class="wishlist-icon-button__icon" aria-hidden="true"><!-- filled heart --></svg>
</button>

6. Wishlist overview: focus management when removing items

On the actual wishlist page, every click on the remove button takes a complete list item out of the DOM. Without deliberate focus management, the currently focused element disappears along with it, and keyboard focus falls back uncontrolled to the top of the document or the body, causing users to lose their place in the list.

It is more robust to move focus deliberately to the remove button of the next list item after removal, or, if no items remain, to a heading or an empty state message like "Your wishlist is empty". In addition, an aria-live="polite" region should confirm which item was removed, so the action remains traceable without visual observation.

7. Moving to cart: announcing two events within one action

The "move to cart" action technically triggers two events at once: the item disappears from the wishlist and appears in the cart. If only one of the two events is announced, for example only a generic cart confirmation, it stays unclear to screen reader users that the item was simultaneously removed from the wishlist.

A clear, combined announcement like "Item was moved to the cart and removed from the wishlist" in the same live region used for a plain removal action avoids this misunderstanding and prevents users from later mistakenly looking for the same item, assumed still present, on the wishlist.


function moveToCart(productId) {
  removeFromWishlist(productId);
  addToCart(productId);

  document.getElementById('wishlist-status').textContent =
    'Item was moved to the cart and removed from the wishlist.';
}

8. Building multiple wishlists and shared lists accessibly

Stores with multiple named wishlists, for example for different occasions, or with a share by link feature need additional status announcements. Creating a new list should move focus directly to the new name field, while copying a share link should be confirmed through the same aria-live technique already used for the wishlist toggle, for instance with the text "Link was copied to the clipboard".

With multiple lists, the wishlist button on the product view should clearly convey which list an item is currently saved to, for example via a dropdown with checkbox semantics instead of a simple toggle button, since this is no longer a plain yes or no decision but a multi select across several lists.

9. Implementation in Magento and Hyvä: an Alpine.js component for the wishlist button

In Hyvä, the wishlist toggle can be implemented as a compact Alpine.js component that holds the saved state, updates the label in sync with aria-pressed, and uses a central live region in the layout for the status announcement. The component should be reused in the product listing, the product detail page and the wishlist overview, so behavior does not differ from page to page.

Every inline script block for the status announcement or for toggling aria-pressed must be registered via $hyvaCsp->registerInlineScript() in the corresponding phtml template, so the Content Security Policy does not block execution.


document.addEventListener('alpine:init', () => {
  Alpine.data('wishlistToggle', (productId, initialPressed) => ({
    pressed: initialPressed,
    toggle() {
      this.pressed = !this.pressed;
      this.$dispatch('wishlist-status', {
        message: this.pressed
          ? 'Item was added to the wishlist.'
          : 'Item was removed from the wishlist.',
      });
    },
  }));
});
Element Attribute/technique Purpose Common mistake
Wishlist button aria-pressed true/false Announcing state for screen readers Attribute missing entirely
Icon only button aria-label per state Providing an accessible name without visible text Static label despite state change
Status change aria-live=polite region Immediate confirmation of the action Only a visual change with no announcement
Removing an item Focus on next element Preserving orientation within the list Focus falls back to the top of the document
Moving to cart Combined live announcement Communicating both events at once Only a cart confirmation with no wishlist mention

Mironsoft

WCAG audits, accessible Magento shops, and training

Not sure whether the shop is actually accessible?

We audit existing Magento shops against WCAG 2.2, fix concrete barriers in the Hyvä frontend, and train teams so accessibility stays anchored in the development process for good.

WCAG Audit

Systematically review the shop against WCAG 2.2 AA, with a prioritized issue list.

Fixing Barriers

Concrete implementation: keyboard operability, screen reader support, contrast, forms.

Team Training

Raise developer and editor awareness for accessible implementation day to day.

10. Summary

Accessible Wishlists: The Essentials

Core idea

aria-pressed instead of aria-checked correctly marks the wishlist button as a toggle button with two states.

Double redundancy

A switching label or aria-label combined with aria-pressed and a live region secures the announcement across different screen readers.

Focus management

Removing an item from the wishlist list requires moving focus deliberately to the next element or an empty state message.

Combined actions

Moving to cart affects two lists at once and needs a correspondingly combined status message.

11. FAQ: Accessible Wishlists: The Essentials

1Which ARIA attribute fits a wishlist button?
aria-pressed, not aria-checked. aria-checked belongs to radio buttons and checkboxes, aria-pressed is meant for a standalone toggle button with two states.
2Is a switching icon alone enough as a status indicator?
No, a purely visual icon communicates the state only to sighted users. aria-pressed and a switching label or aria-label are additionally needed.
3Why does an icon only wishlist button need an aria-label?
Without visible text the button otherwise has no accessible name. aria-label takes over that role and should be updated per state.
4Does a change to aria-pressed get read out automatically by the screen reader?
Not reliably in every combination. An additional aria-live=polite region provides a consistent, immediate announcement.
5What happens to keyboard focus when an item is removed from the wishlist?
Without focus management it falls back uncontrolled. It is better to move focus deliberately to the next list item or an empty state message.
6Do I need to issue two separate messages when moving an item to the cart?
No, a combined message like Item was moved to the cart and removed from the wishlist is clearer than two separate announcements.
7How should copying a wishlist share link be announced?
Through the same aria-live technique used for the toggle, with a short confirmation text like Link was copied to the clipboard.
8Is a simple toggle button still appropriate with multiple named wishlists?
Rarely. With multiple lists there is no longer a simple yes or no decision, a dropdown with checkbox semantics per list fits better then.
9Should the live region for wishlist status messages be visible?
Not necessarily, sr-only is sufficient. What matters is that the text is replaced rather than appended on every change.
10Is a color change of the heart icon enough as the only visual signal?
It should not be the only cue. An additional fill pattern or a small badge helps users with limited color perception.