Skip to content

Accessibility

The components ship accessible by default - you don't add ARIA for the basics:

The components ship accessible by default - you don’t add ARIA for the basics:

  • Volume-discount tiers are a keyboard radiogroup. volume-tier-list sets role="radiogroup"
    • an aria-label (the discount title, unless you supply your own aria-label/aria-labelledby); each volume-tier row is a role="radio" with aria-checked, roving tabindex (the selected row is the single tab stop), and ss-active, driven by Arrow/Home/End/Space with selection following focus (the same radio-group pattern as change-option swatch mode). The row’s accessible name comes from its show-tier-* text.
  • Mix & match sections are an APG disclosure. toggle-section resolves the focusable control (your nested <button>, else the upgraded host) and sets aria-expanded + aria-controls pointing at the section’s mix-match-item-list, which owns the matching deterministic id and the native hidden while collapsed - so a collapsed pool is genuinely removed from the accessibility tree, not just visually hidden. show-section-progress is a real role="progressbar" (aria-valuemin / -valuenow / -valuemax) toward the section’s requirement. add-mix-match-item and add-mix-match-to-cart project availability / capacity / completeness as aria-disabled on their control. show-pick-image writes alt = the product title; show-section-image is decorative (alt=""). Not automatic: a name for the icon-only steppers - see “What you still own” below.
  • The add-ons row toggle is native-first. select-addon uses a nested <input type="checkbox"> as its control when you provide one (the recommended markup - pair it with a label or give the input an aria-label); a nested <button>/<a> becomes an APG toggle button (aria-pressed); a bare host upgrades itself to role="checkbox" + tabindex + Enter/Space with aria-checked tracking the row. Availability projects as aria-disabled on the control (ticking ON is blocked while the card’s variant is unbuyable; ticking OFF always works) with ss-out-of-stock / ss-unavailable on the wrapper for CSS.
  • The subscription selectors are APG toggle buttons - on the PDP and in the cart alike. All four (change-purchase-option / change-selling-plan and their in-cart counterparts change-line-purchase-option / change-line-selling-plan) resolve a focusable control per option (your nested <button> if you provide one, else the purchase-option / selling-plan-option host upgraded to role="button" + tabindex + Enter/Space) and set aria-pressed on it - mirroring [ss-active], written deterministically every update - so the current mode / active frequency is exposed to AT. The two pairs behave identically; the only difference is scope (product vs cart line). The SSR fill stamps the same attributes in the same order, so a server-rendered PDP selector is keyboard-operable before hydration too. Their names come from the option’s own text (the pill label; the engine-filled show-selling-plan-name / show-selling-plan-price), so give each purchase-option visible text. As everywhere else in the library, a nested native <button> is respected and preferred (see “prefer nesting a native <button>/<a>” below) - but a bare pill is now a real control either way.
  • Action/nav controls are keyboard-operable and named. add-to-cart, buy-now, add-bundle-to-cart, add-volume-to-cart, open-cart, close-cart, the quantity steppers, the cart-line controls, show-thumbnail, and product-link resolve a focusable control: a nested native <button>/<a> if you provide one (recommended - the element then projects sold-out/disabled state + the label onto it), else the element upgrades itself to role="button"/"link" + tabindex + Enter/Space. Icon controls get an aria-label automatically (“Increase quantity”, “Remove item”, …).
  • show-thumbnail exposes the selected thumbnail via aria-current; images write alt deterministically; external-video <iframe>s get a title.
  • Inputs are labelled - input-quantity (aria-label="Quantity" + min/max) and search-input (aria-label, override via the label attribute).
  • Markets pickers name their control - in select mode change-country / change-language set an aria-label on the generated <select> (from the label attribute, else "Country"/ "Language") when the author supplies no name and disable it during a switch. In template (pill) mode the picker makes the pill container a keyboard radiogroup: role="radiogroup"
    • aria-label (the author’s own aria-label/aria-labelledby wins, else the label attribute), each locale-value gets role="radio" + aria-checked, roving tabindex (the selected pill is the single tab stop), Arrow/Home/End/Space keys with selection following focus (identical to change-option swatch mode), and aria-disabled on the pills while a switch is in flight. Both modes set aria-busy on the picker during the switch and inject a visually-hidden role="status" live region announcing the settled market/language.

When building or auditing, prefer nesting a native <button>/<a> in the action/nav tags, and don’t hand-add role/tabindex to them - the library does it. The full standard (WCAG 2.2 AA + ARIA APG, by archetype) is the library’s accessibility guide. All three change-option modes are accessible: swatch is a keyboard radiogroup (Arrow keys + roving focus, aria-checked, aria-disabled on unavailable values); the <select> and native-radio modes get a programmatic group label (role="radiogroup"/aria-label) plus the unavailable state projected onto the native control (aria-disabled on radios, disabled on unavailable <option>s - combinations that don’t exist or are out of stock, the same unavailability the swatch/radio modes mark). (Still in progress: the cart drawer dialog and the predictive-search combobox.)

What you still own (the library does NOT do it for you): two things, both enforced as SSK-501.

  1. Unnamed fields. A few controls use your <input>/<textarea> verbatim and never name it
    • apply-discount, cart-note, klaviyo-back-in-stock, and change-price. Give each an accessible name (aria-label, a wrapping <label>, or a <label for>; a placeholder is not a name).
  2. The icon-only mix & match steppers. increase-mix-match-item, decrease-mix-match-item, and remove-mix-match-item are deliberately not self-labelled (a generic label would be wrong per card), so ”+” / ”−” / ”×” is all a screen reader gets. Put an aria-label on the element or its nested <button> - e.g. aria-label="Increase quantity". (add-mix-match-item normally carries visible text and needs no extra label.)

Also provide real alt on any images you add outside the show-* media tags, and keep a sane heading order.


Imported from the Storesynk skill v0.1.0 (references/component-reference.md). To change this page, change it there.