- Load More failures are VoiceOver choreography issues—focus stuck on the button, silent appends, one-shot polite regions—not markup scanners catch.
- Write acceptance criteria before code review: announce count, move focus to first new card, scroll into view, repeat every press.
- Defer React focus until after DOM commit; gate focus moves so late network returns do not yank a shopper mid-browse.
- Polite live regions must change in a way AT notices on every append; assertive catalog shouts fight cart/filter chrome.
- Retest presses 1, 2, and last page on shared previews, then re-scan—illegal ARIA loves to follow “good” announcements.
In Green Scans Aren’t Enough, we opened this Converge Accessibility craft series with a blunt observation: automated WCAG tools go green while screen-reader shoppers still hit dead ends. Nowhere is that gap louder than on a product listing’s Load More control.
Visually, Load More is trivial—append a batch of cards, maybe nudge the button down. For VoiceOver on a Hydrogen/React storefront, it is a choreography problem: who speaks, when focus moves, and whether the next press still works. This post is the remediation craft we learned while turning a Converge Accessibility WCAG 2.2 finding into something you can retest on every sibling brand.
What “broken” actually sounds like
When Load More fails for assistive technology, it rarely looks broken in Chromium DevTools. Typical failure modes from expert audit + client VoiceOver retests:
Focus sticks on the button. The grid grows, but keyboard and VoiceOver focus stay on Load More. The shopper has to hunt for the first new card.
Silent append. No live-region announcement that products loaded. Browse-mode users discover the new content only by accident.
One-shot politeness. The first Load More announces a count; subsequent presses go quiet because the live region’s text did not change in a way the AT notices.
Announcement pile-up. Count, card title, and button state race each other—VoiceOver interrupts itself and the shopper loses the plot.
Scanners miss most of this because the DOM after the click is “complete enough.” Experience is not.
Acceptance criteria before the pull request
Borrow the discipline from post 1: write screen-reader acceptance criteria before code review. For Load More on a Hydrogen PLP, our working definition of done looked like this:
After each successful Load More, announce how many products were appended (or the new total range—pick one pattern and stick to it).
Move focus to the first newly revealed product card (or its primary link), not back onto Load More.
Scroll that focused card into view so sighted keyboard users and VoiceOver users share the same “you are here.”
Repeat the announce + focus sequence on every subsequent Load More—not only the first.
If the button disables at the end of the catalog, announce that state change; do not leave a dead control that still looks pressable.
Client VoiceOver feedback caught previews that only announced a count once. That intermittent polite-region failure is exactly what automated tools will not schedule for you.
Focus management on React grids
Hydrogen storefronts append product cards through React state. Focus is not free.
Practical patterns that held up under retest:
Stable identity for the first new card. Keep a ref or data attribute for the first item in the newly fetched batch. After paint, call
focus()on a naturally focusable child (product link) rather than a non-interactive wrapper.Defer focus until after DOM commit. Focusing in the same tick as
setStateoften lands on a node that is about to remount.requestAnimationFrameor a post-commit effect with a guard for “batch just arrived” is more reliable than hoping.Do not steal focus mid-browse. If the user has already moved away from Load More before the network returns, do not yank them to a card. Gate the focus move on “Load More was still the last intentional control” or an explicit loading state tied to that click.
Preserve Tab vs rotor differences. The same card markup can announce twice under Tab and once under VoiceOver rotor. Document which mode is the acceptance test so “VoiceOver passed” means something.
Live regions that keep working on the fifth press
Polite live regions are the usual vehicle for “12 more products loaded.” They are also fragile.
Craft notes from remediation:
Change the accessible name, not just the visual text. If you rewrite the same string, some AT + browser pairs stay silent. Include a batch id, timestamp fragment, or alternating spacer so each append is a distinct update.
aria-live="polite" over assertive for catalog appends. Assertive interruptions fight cart drawers, toasts, and filter chips that may already be talking.
Clear, then set—or use a dedicated announcer node. Emptying the live region briefly before writing the next message is a known pattern when repeated identical updates fail. Prefer a small announcer utility shared across brands over ad-hoc divs in every route.
Order the sentences. Count first, then focused item name—or the reverse—but never both racing with the button’s own state change. Sequence beats volume.
When ARIA fights you, remember the series rule from post 1: no ARIA is better than bad ARIA. A quiet, correctly focused grid beats a noisy live region that lies.
Hydrogen-specific pitfalls
Headless Shopify stacks add a few traps scanners will not narrate:
Suspense and streaming. Partial UI that rehydrates can reset focus or remount the live region. Keep the announcer outside the suspended product grid when you can.
Shared cart/filter chrome. Global drawers with their own live “N items” messages can interrupt Load More announcements. Triage announcement priority by journey: PLP append vs cart update should not shout over each other blindly.
Sibling brands, same bug. If two Hydrogen storefronts share a product-grid package, fix Load More once in the shared module. Porting by copy-paste is how the fifth Load More stays broken on brand B after brand A shipped the fix—more on that in post 4.
Retest loop that matches Converge findings
Turn the audit line into a task with severity, surface area, and a retest path—then actually walk it:
Reproduce on a shared Oxygen/preview URL with VoiceOver (and NVDA where the client cares).
Verify count announcement + focus target + scroll-into-view on presses 1, 2, and “last page.”
Re-run SortSite/axe after the VoiceOver pass. Illegal
aria-*and contrast regressions love to sneak in behind “good” announcements.Capture a short acceptance clip or checklist in the ticket so sibling-brand QA is not reinventing the sequence.
What’s next
Post 3 covers ARIA discipline—promo badges, product cards, and illegal names scanners miss but auditors catch. Post 4 is the multi-brand story: one Converge backlog across Hydrogen brands without forking forever.
If your PLP Load More looks fine in Lighthouse and fails in VoiceOver, we should talk. Endertech remediates Converge Accessibility findings on headless Shopify Hydrogen work. Start at endertech.com/contact or explore our Shopify Hydrogen solutions.
Series note: Client brand names are anonymized. Converge Accessibility is named with approval as the contracted expert auditor. Pattern details are paraphrased from internal remediation—not pasted from tickets.
