Infinite scroll loads more content as you approach the bottom of a list. For a mouse or a thumb that barely registers. For a keyboard user, a screen reader user, or anybody who came for something in the footer, it creates problems that are unusually hard to solve once the pattern is in place.
Before the problems, the thing almost nobody publishes. W3C has a design pattern for this exact widget, and it exists because infinite scroll creates usability and interoperability difficulties for assistive technology users. It is worth knowing about even if you decide against it, because every fix below has a counterpart in it.
There Is a Pattern For This, and It Is Called a Feed
The feed pattern establishes a contract between your page and the screen reader, so somebody can read and trigger the loading of more content while staying in their normal reading mode. That last part is the whole idea. A feed is a structure rather than a widget, which is what lets a screen reader keep its reading cursor working instead of switching into the application mode where the reading keys stop doing anything. That switch is the real reason infinite scroll is hard with a keyboard, and it is a better answer than calling it hard.
role="feed"on the container, with a label, androle="article"on each unit of content, each with its own label pointing at something inside it that identifies it.aria-posinsetandaria-setsizeon each article, so somebody hears where they are in the set.aria-setsizecan be the number loaded so far, or the total, or-1where the total is genuinely undetermined, which is most feeds and is the case our old advice had no answer for.aria-describedbyon each article, strongly recommended, pointing at the primary content, so somebody can move article to article and judge which ones are worth stopping on.aria-busy="true"while a batch is being added, then false when it is done. The pattern calls this extremely important, and it is the mechanism that stops a multi-step DOM update announcing itself in pieces.- Keyboard conventions, offered as conventions. Page Down for the next article, Page Up for the previous, Control and End to move to the first focusable element after the feed, Control and Home to move to the first one before it. W3C acknowledges that no convention is established here, so these are a proposal rather than behavior anybody will expect.
One honest caveat before you build on it. Browser and screen reader support for role="feed" is uneven, and we have not tested it ourselves across the pairs that matter. A pattern that is not supported in practice is worse advice than a live region that is, so treat this as the direction to move in and verify it against your own audience before you rely on it.
The Footer Problem
Content loads faster than a keyboard user can reach the bottom, so the footer moves away every time it comes close. Contact details, terms, the accessibility statement, all become effectively unreachable by the route people expect to use.
The feed pattern's Control and End binding is, on paper, exactly the fix for this, because it moves focus to the first focusable element after the feed. Whether a given screen reader implements it is the open question. Until that is settled, the reliable answers are the blunt ones. Stop loading automatically after a few batches. Duplicate the essential footer links somewhere that does not move. Or use a different pattern.
Tabbing through an endlessly growing list to reach anything past it is not a usable route, and it is a usability obligation rather than a WCAG one, because 2.4.1 Bypass Blocks is scoped to content repeated across pages and does not reach a single page's own feed. If the content past the list can be reached at all, with enough presses, the keyboard rule is satisfied and the problem is that nobody would.
Whatever route you take, give people something to jump with. A heading before the feed, a landmark around it, a way to collapse it, or a link that moves focus past it. Any of those is worth more than none, and a skip link does not have to be permanently visible. It may be hidden until it receives keyboard focus, which is what removes the usual design objection to one.
Announce the Count, Not the Arrival
Here is a distinction we had slightly wrong. New items appearing is not itself a status message. W3C says directly that a list of results is not a status update, and that the brief message about it is. So 20 more results loaded, 60 of 340 shown, is the thing 4.1.3 covers, and the sixty new cards are not.
The criterion also does not force you to invent messages. Its stated purpose is to make the messages you do display reachable, so a feed that shows no loading text and no count passes it without doing anything and still leaves a screen reader user with no idea whether anything happened. That is why what follows is advice rather than a requirement, and it is the advice that makes the pattern usable.
- Announce once, with a number. One message telling somebody where they are beats a start and a finish that carry nothing between them.
- Set
aria-atomic="true"on the region. Without it, a region where only the number changes announces sixty, and a listener has no idea sixty of what. This is the attribute that decides whether your carefully written sentence arrives as a sentence. - Consider
role="log"rather thanrole="status". A log region appends and a status region replaces, and an accumulating feed is exactly the case a log was defined for. - Use
aria-busywhile the batch lands, rather than hand-tuning what fires when. That is the standards answer to a loading message followed by a loaded message, which is two interruptions carrying no information between them.
Keep the Reader Where They Were
Focus has to stay put when new items load. Appending content should never move anybody, and a scroll position that resets on load is the most frustrating version of this bug.
The implementation detail behind that is worth naming, because it is the thing to build rather than the thing to hope for. The feed pattern divides the work explicitly. The page is responsible for scrolling appropriately based on which article holds focus, and for loading or removing articles based on which article holds focus. So the load trigger should be aware of where focus is, not only of where the scrollbar is. A trigger watching scroll position alone is what yanks a keyboard user around.
Load More Is Usually Better
A button that loads the next batch solves nearly every problem above at once. People get real control, it is keyboard reachable by default, it gives you a natural place to move focus, and the footer stays where it was put.
| Pattern | Footer reachable | Keyboard | Deep linking |
|---|---|---|---|
| Infinite scroll | No | Hard, because a feed built as a widget pushes the reader out of reading mode | Hard |
| Load more button | Yes | Straightforward | Possible |
| Pagination | Yes | Straightforward | Free |
That last column is worth more than it looks, and it connects to a rule. 2.4.5 Multiple Ways at Level AA asks for more than one route to a page, and a URL that identifies a position is part of how somebody gets back to one. Pagination gives you that for nothing, which is one of the quieter arguments for it.
After the button loads a batch, move focus to the first new item rather than leaving it on the button, so a keyboard user does not tab back through everything they already read. And remember what that choice does to the rest of the page. Moving focus is a change of context, which takes the update out of the status message rule, so a button that moves focus needs no live region and adding one announces the batch twice.
If You Keep Infinite Scroll
- Stop loading automatically after two or three batches and switch to a button.
- Announce the running count through a polite live region, atomic, once per batch.
- Give the list a heading and something to jump past it with, whether that is a landmark, a collapse control, or a link hidden until it is focused.
- Keep the browser URL updated so a position can be shared and returned to.
- Duplicate the essential footer links somewhere that does not move.