Search topics
Infinite scroll that moves the footer away as Tab nears it
A shop's list of mugs adds twelve more ahead of the footer whenever its end comes into view, with its loading line in a plain div, so Tab meets new mugs each time it nears the footer and a screen reader is never told that more arrived.
observer.observe(sentinel); The problem
Keyboard users can't reach the links under a list that loads more as it scrolls, or lose their place each time it loads, and screen reader users are never told that more items arrived.
A shop's category page lists its 48 mugs, twelve at a time. There are no page links and no button: as the end of the list comes into view, a "Loading more mugs…" line shows under the last row, and a moment later twelve more mugs appear and the line goes. For someone scrolling with a mouse or a finger, the list just keeps going, which is what the design is for. Under it, the footer holds Contact us and Returns.
Put the mouse aside and press Tab to get to Contact us. Focus moves from mug to mug. Each time it reaches the last rows, the page scrolls to show the focused mug, the end of the list comes within reach, and twelve more mugs are added between focus and the footer. The next Tab goes into them. Contact us comes only once all 48 mugs have loaded and Tab has passed every one of them; on a list with no end, it never comes. Meanwhile a screen reader says nothing: the "Loading more mugs…" line comes and goes in a plain div, and the new mugs arrive in silence.
A second version of the page adds each batch with list.innerHTML += html. That line rebuilds every mug in the list, so the mug that had focus is removed along with the rest, and focus is left on the page itself. In Chrome, the next Tab goes back to the first mug. Every twelve mugs, a keyboard user is sent back to the start of the list, and a screen reader user loses the place they were reading.
Success criterion 2.1.1 Keyboard asks that everything the page offers can be done from the keyboard. Contact us and Returns are ordinary links, but Tab reaches them only when there is nothing left to load, because the list grows ahead of focus each time it nears them. On this page that means passing all 48 mugs; on a list with no end, the footer's links can't be reached from the keyboard at all. A screen reader's landmark keys can jump straight to the footer, since it is in the page all along; Tab has no such key.
Success criterion 2.4.3 Focus Order asks that focus moves through the page in an order that keeps its meaning and its use. When the list is rebuilt, the focused mug no longer exists, and the browser has nowhere to keep focus but the page itself. Where the next Tab goes is then the browser's choice, not the page's: in Chrome it is the first mug. The order of the mugs hasn't changed, but the person's place in it is lost every time more load.
Success criterion 4.1.3 Status Messages asks that a message about the page's state, such as that it is waiting for more, reaches a screen reader without taking focus. "Loading more mugs…" is such a message, shown in a plain div with no role, so a screen reader is never told about it. That is failure F103. The mugs themselves are content, not a status message, and 4.1.3 doesn't ask for each to be announced; a count of how many are shown is a status message, when the page shows one.
Infinite scroll is common on shop category pages, search results, news sites and social feeds, especially in phone layouts, and it often comes from one shared list component, so the failure repeats on every list the site has. The footer it moves away often holds the links people come looking for when something goes wrong: contact, delivery, returns, the accessibility statement.
2 of 10 groups affected
- No vision (affected)
- Low vision (not affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (affected)
- Reach, strength (not affected)
- Cognitive (not affected)
- Seizures (not affected)
- Limited manipulation Keyboard and switch users press Tab towards the footer, and twelve more mugs arrive ahead of them each time they near it, so Contact us and Returns come only after every mug, or never on a list with no end. When the list is rebuilt, focus is dropped and the next press starts again at the first mug.
- Without vision Screen reader users hear nothing while more mugs load or when they arrive, so they can't tell whether the list grew or ended. When the list is rebuilt, the mug they were on is gone and they lose their place in it.
Every mug is a link with a name, and the footer is a footer. Nothing in the markup is invalid, so axe has nothing to report: whether the footer can be reached, and whether focus survives a load, only show up while the page is used.
EaseWeb's audit scrolls the page to its end and counts the links ahead of the footer, then focuses the last of them, as Tab would leave it. A list that grows by itself is sent to review, since only a person can tell whether the list ends soon, or whether a skip key exists. A focused link that the load removes, leaving focus on the page, is reported as a 2.4.3 failure, because code alone proves it. A loading line on a page with no live region is sent to review too. No check can tell whether what is announced makes sense to a listener.
An AI fix often reaches for ARIA here. Adding aria-live="polite" to the whole list makes a screen reader read out all twelve new mug names each time, which is noise, not a count. Adding role="feed" alone changes nothing for someone using Tab: the keys that move past a feed come from the page's own script.
Try it
Reach Contact us, under the list of mugs, using only the keyboard.
Broken 1: press Tab through the mugs. Each time focus nears the end, twelve more arrive ahead of the footer, and the panel shows a screen reader hearing nothing.
Broken 2: the same list, rebuilt each time it loads. Focus drops off the mug it was on, and the next Tab starts again from the first mug.
Apply the fix. With the button, Tab past it to Contact us, or press it to load more; in the feed, press Ctrl + End from any mug.
Broken Can you fix this?
Screen reader says:
(Tab towards the footer)
The fix
Load more when people ask for it, add the new items without rebuilding the list, and say how many are shown in a status line.
- 1Let the keyboard reach what comes after the list: load more with a button after it, or give a feed a key that skips past it.
- 2Add new items after the ones already shown; never rebuild the list under focus.
- 3Put the loading message and the new count in a status line that is on the page from the start.
Mugs that load as the page scrolls
<main> <h1>Mugs</h1> <ul class="products" data-total="48"> <li> <a href="/mugs/enamel-white"> <img src="/images/enamel-white.jpg" alt=""> Enamel mug, white </a> </li> … </ul> <div class="loading" hidden>Loading more mugs…</div> <div class="sentinel"></div></main><footer> <a href="/contact">Contact us</a> <a href="/returns">Returns</a></footer>A Load more button and a status line
<main> <h1>Mugs</h1> <ul class="products" data-total="48"> <li> <a href="/mugs/enamel-white"> <img src="/images/enamel-white.jpg" alt=""> Enamel mug, white </a> </li> … </ul> <p class="count" role="status">Showing 12 of 48 mugs</p> <button type="button" class="load-more">Show 12 more</button></main><footer> <a href="/contact">Contact us</a> <a href="/returns">Returns</a></footer>Why this fix: Change the page only when people ask
The empty div after the list is watched, and once it comes within 200 pixels of the window, twelve more mugs are fetched and added ahead of the footer. Focusing one of the last mugs scrolls the page far enough to trigger it, so Tab always finds more mugs before the footer. The loading line is shown and hidden in a plain div, which a screen reader is never told about.
Nothing loads until the button is pressed, so the footer stays where it is: one Tab after the last mug and the button. The button is a native one, so Enter and Space press it with no key handling to write. The new mugs are added after the old ones, so nothing that had focus is removed, and focus moves to the first new mug, where the person wanted to go on reading. The status line is on the page from the start, so a screen reader announces "Loading more mugs…" and then "Showing 24 of 48 mugs" as they are written, without focus going there. It sits beside the button that changes it, where people look for it. When every mug is shown, the button goes and the line says "Showing 48 of 48 mugs".
Both pass, but use the Load more button. It does the whole job with a native button and a few lines of script, it works the same for the mouse, the keyboard, a screen reader and voice control, and people decide when more mugs come, so the footer is always one Tab past the button. The feed keeps the endless scroll, at the cost of keys that a person has to learn and a pattern whose support varies, which is why it is marked as not enough on its own; choose it only when the design must load as it scrolls, and test it with the screen readers your visitors use. Numbered page links pass too, and a page of results people can return to, bookmark or share is often better still; how to build them so they say where they go is covered in pagination links that don't say which page or where they go.
A framework can drop focus the same way innerHTML += does. A list whose items have no stable key, or that is swapped for a new list each time more load, is rebuilt from scratch, and the focused item goes with it. Key each item by something that belongs to it, such as the product id, and add the new page to the end of the same list.
After Load more, focus goes to the first new mug, not the button. Left on the button, focus would sit after the new mugs, so the next Tab would skip them all to reach the footer, and people would have to go back to find what they asked for. The count is written after focus moves, so the new mug's name comes first and the polite count waits behind it.
A slow or failed load needs saying too: write "Couldn't load more mugs. Try again." into the same status line, and leave the button where it is. What to announce while something loads, and when, is covered in loading spinners that screen readers never announce. When filters or sorting replace the list, the count of results is the message to announce, as search and filter results that update without a word explains.
The button can begin as a link to the next page, such as /mugs?page=2, so it still works before the script has loaded or when it fails, and the script can turn it into a button. Writing the number of mugs shown into the address with history.replaceState() lets Back and a shared link return to the same place in the list.
Some lists load the first batch or two as people scroll and then show a Load more button. That passes, as long as the button appears after a fixed number of loads, so the footer is reached after a known number of mugs. A feed with no known total gives aria-setsize="-1". A feed that removes old articles as new ones load must keep every article's aria-posinset right, and must never remove the one that has focus. Many Mac keyboards have no End, Page Up or Page Down key and give them as Fn with an arrow key, so test the feed's keys there too.
Design ideas
The end of a list has more than one look.
Three more ways to end the guide’s list of mugs that keep the footer within reach, and two fashionable ones that don’t. Tab through each, and press its buttons.
In the design file
- Draw what comes after the last row, before the footer, as a button or page links, never as more rows that arrive by themselves.
- Write how many are shown in words beside the button, such as “Showing 24 of 48”, and mark it as the line that is read out when it changes.
- Draw the button while more load, still in its place with its label and its focus ring.
- Mark the first new item as the place focus goes once more have loaded.
Passes
- Faded next row with a button over it The next row shows through a white wash, with an outlined pill on top, so the eye sees there is more. Nothing comes until the pill is pressed, so the footer stays one Tab past it. The faded mugs are a picture, not links.
24 of 48 mugs
Progress line over an outlined pill “24 of 48” in words over a thin bar, and an outlined pill under them. The words are the status line, read out when they change; the bar only repeats them, so it can stay as quiet as the design wants.- Compact page switcher Previous and Next around “Page 2 of 4”, in one rounded bar. Each page is a page of its own, so nothing loads under the person, the footer never moves, and a page can be bookmarked. The arrows need their names in words.
Fails
Contact us · Returns
Placeholder cards that load by themselvesWCAG 2.1.1 Tab to the last mug: grey placeholder cards appear under it by themselves and turn into mugs. It looks smooth, but every time Tab nears the end, a new row arrives ahead of the footer. Keep the placeholders, and show them after a press of the button.- Pill that turns into a spinnerWCAG 2.4.3 Press it: while more load, the pill is swapped for a spinner, so the button that had focus is gone and focus goes with it. When the pill comes back, focus is on no control. Keep the button where it is, and put the spinner inside it.
Verify the fix
5 checks, no mouse.
Test with: Keyboard, Screen reader
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the list, its component or its library changes.
How screen readers handle role="status", role="feed", aria-posinset, aria-setsize and aria-busy="true"
Limits
This is a starting pattern, not a guarantee for every list. The scripts are minimal sketches: they don't handle errors, filters, sorting or the browser's Back button. A list drawn by a virtualising library that removes rows as they scroll out of view needs its own checks. Product cards with more than one control each, such as a link and an Add to basket button, at least double the presses to get past the list, which makes a Load more button matter more. A scrolling panel inside the page that loads more as it scrolls is reached as described in scrollable areas that a keyboard can't scroll. Test the complete journey with the assistive technology your visitors use.
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.
References
- WCAG Understanding: keyboard (opens in a new tab)
- WCAG Understanding: focus-order (opens in a new tab)
- WCAG Understanding: status-messages (opens in a new tab)
- WCAG Technique F103 (opens in a new tab)
- WCAG Technique ARIA22 (opens in a new tab)
- ARIA APG: feed (opens in a new tab)
- ARIA APG: feed (opens in a new tab)
- developer.mozilla.org: feed_role (opens in a new tab)
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.