Accessibility pattern · Data display

Feed with load more

Each post is a focusable article with a name, a description and its place in the list; Page Down and Page Up move between posts, and Control with End or Home leaves the feed. More posts come when you ask, with a button rather than infinite scroll, and new ones wait behind a pill that never moves the page or takes focus.

WCAG criteria
7
Keyboard rules
6
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Team activity

Design team, Bengaluru

Notification settings

Asha Rao

Checkout redesign is live

The new checkout went out to every customer this morning. Card and UPI now share one form, and errors are listed at the top when you submit.

6 comments · 14 reactions

Kabir Mehta

September audit: 14 issues left

We fixed 31 of the 45 issues from the September accessibility audit. Most of the rest are chart colors; owners are in the tracker.

3 comments · 9 reactions

Meera Iyer

Icon set 2.0 is in the library

Forty new icons, all drawn on the same 24-pixel grid, and named for what they mean rather than what they show.

11 comments · 22 reactions

Ravi Shah

Team lunch moves to Thursday

The place in Indiranagar is shut on Wednesdays, so lunch is now Thursday at 1 pm. Same table, same booking.

4 comments · 8 reactions

Posts older than 30 days are archived.

Open the archive

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
TabMoves to each post in turn and to the link inside it, then to Load older posts.
Page DownMoves focus to the next post, from anywhere inside the current one.
Page UpMoves focus to the previous post.
CtrlEndLeaves the feed for the first thing after it: Load older posts, or the archive link once every post is in.
CtrlHomeLeaves the feed for the last thing before it: the new posts pill when it shows, otherwise Notification settings.
Enter or SpaceOn Load older posts, adds the next posts and moves focus to the first of them; on the pill, shows the new posts and moves focus to the newest.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
Tab reaches the first postCheckout redesign is live, article. Asha Rao, 18 min ago. The new checkout went out to every customer this morning.
Page Down moves to the next postSeptember audit: 14 issues left, article
Load older posts is pressedLoading older posts
The posts arrive and focus moves to the first of themUsability sessions booked for next week, article. 4 older posts loaded.
New posts arrive while someone is reading3 new posts at the top of the feed (focus stays where it is)
The last page arrives3 older posts loaded. You're all caught up.

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<div class="ap-feed" data-ap-feed>
  <div class="ap-feed__top">
    <div>
      <h3 class="ap-feed__name" id="feed-name">Team activity</h3>
      <p class="ap-feed__sub">Design team, Bengaluru</p>
    </div>
    <a class="ap-feed__settings" href="#feed-settings">Notification settings</a>
  </div>

  <div class="ap-feed__scroll" data-ap-scroll>
    <div class="ap-feed__pillbar">
      <button type="button" class="ap-feed__pill" data-ap-show-new hidden><svg class="ap-feed__pill-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 19V5M6 11l6-6 6 6"/></svg>Show 3 new posts</button>
    </div>

    <div class="ap-feed__list" role="feed" aria-labelledby="feed-name" aria-busy="false" data-ap-list>
      <article class="ap-feed__post" tabindex="0" aria-posinset="1" aria-setsize="-1" aria-labelledby="feed-p1-t" aria-describedby="feed-p1-m feed-p1-d">
        <span class="ap-feed__avatar ap-feed__avatar--accent" aria-hidden="true">AR</span>
        <div class="ap-feed__body">
          <p class="ap-feed__meta" id="feed-p1-m"><span class="ap-feed__author">Asha Rao</span> <span aria-hidden="true">·</span> <time datetime="2026-10-05T09:42">18 min ago</time></p>
          <h4 class="ap-feed__subject" id="feed-p1-t"><a href="#feed-post-1">Checkout redesign is live</a></h4>
          <p class="ap-feed__text" id="feed-p1-d">The new checkout went out to every customer this morning. Card and UPI now share one form, and errors are listed at the top when you submit.</p>
          <p class="ap-feed__stats">6 comments · 14 reactions</p>
        </div>
      </article>
      <article class="ap-feed__post" tabindex="0" aria-posinset="2" aria-setsize="-1" aria-labelledby="feed-p2-t" aria-describedby="feed-p2-m feed-p2-d">
        <span class="ap-feed__avatar ap-feed__avatar--info" aria-hidden="true">KM</span>
        <div class="ap-feed__body">
          <p class="ap-feed__meta" id="feed-p2-m"><span class="ap-feed__author">Kabir Mehta</span> <span aria-hidden="true">·</span> <time datetime="2026-10-05T09:00">1 h ago</time></p>
          <h4 class="ap-feed__subject" id="feed-p2-t"><a href="#feed-post-2">September audit: 14 issues left</a></h4>
          <p class="ap-feed__text" id="feed-p2-d">We fixed 31 of the 45 issues from the September accessibility audit. Most of the rest are chart colors; owners are in the tracker.</p>
          <p class="ap-feed__stats">3 comments · 9 reactions</p>
        </div>
      </article>
      <article class="ap-feed__post" tabindex="0" aria-posinset="3" aria-setsize="-1" aria-labelledby="feed-p3-t" aria-describedby="feed-p3-m feed-p3-d">
        <span class="ap-feed__avatar ap-feed__avatar--success" aria-hidden="true">MI</span>
        <div class="ap-feed__body">
          <p class="ap-feed__meta" id="feed-p3-m"><span class="ap-feed__author">Meera Iyer</span> <span aria-hidden="true">·</span> <time datetime="2026-10-05T07:10">3 h ago</time></p>
          <h4 class="ap-feed__subject" id="feed-p3-t"><a href="#feed-post-3">Icon set 2.0 is in the library</a></h4>
          <p class="ap-feed__text" id="feed-p3-d">Forty new icons, all drawn on the same 24-pixel grid, and named for what they mean rather than what they show.</p>
          <p class="ap-feed__stats">11 comments · 22 reactions</p>
        </div>
      </article>
      <article class="ap-feed__post" tabindex="0" aria-posinset="4" aria-setsize="-1" aria-labelledby="feed-p4-t" aria-describedby="feed-p4-m feed-p4-d">
        <span class="ap-feed__avatar ap-feed__avatar--warning" aria-hidden="true">RS</span>
        <div class="ap-feed__body">
          <p class="ap-feed__meta" id="feed-p4-m"><span class="ap-feed__author">Ravi Shah</span> <span aria-hidden="true">·</span> <time datetime="2026-10-04T16:30">Yesterday</time></p>
          <h4 class="ap-feed__subject" id="feed-p4-t"><a href="#feed-post-4">Team lunch moves to Thursday</a></h4>
          <p class="ap-feed__text" id="feed-p4-d">The place in Indiranagar is shut on Wednesdays, so lunch is now Thursday at 1 pm. Same table, same booking.</p>
          <p class="ap-feed__stats">4 comments · 8 reactions</p>
        </div>
      </article>
    </div>

    <div class="ap-feed__loading" data-ap-loading hidden aria-hidden="true">
      <span class="ap-feed__ghost"></span>
      <span class="ap-feed__ghost"></span>
    </div>

    <div class="ap-feed__more">
      <button type="button" class="ap-btn ap-feed__load" data-ap-more>
        <svg class="ap-btn__icon ap-feed__spin" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M21 12a9 9 0 1 1-6.2-8.6"/></svg>
        <span data-ap-more-label>Load older posts</span>
      </button>
      <p class="ap-feed__end" data-ap-end hidden>You're all caught up.</p>
    </div>
  </div>

  <div class="ap-feed__foot">
    <p class="ap-feed__foot-text">Posts older than 30 days are archived.</p>
    <a class="ap-feed__archive" href="#feed-archive">Open the archive</a>
  </div>

  <p class="ap-feed__vh" role="status" data-ap-live></p>

  <template data-ap-page>
    <article class="ap-feed__post" tabindex="0" aria-setsize="-1" aria-labelledby="feed-p5-t" aria-describedby="feed-p5-m feed-p5-d">
      <span class="ap-feed__avatar ap-feed__avatar--danger" aria-hidden="true">PN</span>
      <div class="ap-feed__body">
        <p class="ap-feed__meta" id="feed-p5-m"><span class="ap-feed__author">Priya Nair</span> <span aria-hidden="true">·</span> <time datetime="2026-10-04T11:15">Yesterday</time></p>
        <h4 class="ap-feed__subject" id="feed-p5-t"><a href="#feed-post-5">Usability sessions booked for next week</a></h4>
        <p class="ap-feed__text" id="feed-p5-d">Six sessions with shop owners in Pune and Jaipur, Tuesday to Thursday. Add your name to the sheet if you want to watch one.</p>
        <p class="ap-feed__stats">2 comments · 5 reactions</p>
      </div>
    </article>
    <article class="ap-feed__post" tabindex="0" aria-setsize="-1" aria-labelledby="feed-p6-t" aria-describedby="feed-p6-m feed-p6-d">
      <span class="ap-feed__avatar ap-feed__avatar--info" aria-hidden="true">AD</span>
      <div class="ap-feed__body">
        <p class="ap-feed__meta" id="feed-p6-m"><span class="ap-feed__author">Arjun Das</span> <span aria-hidden="true">·</span> <time datetime="2026-10-03T14:20">2 days ago</time></p>
        <h4 class="ap-feed__subject" id="feed-p6-t"><a href="#feed-post-6">Dark mode tokens updated</a></h4>
        <p class="ap-feed__text" id="feed-p6-d">Surface and border tokens now pass 3:1 against each other in dark mode. Pull the latest package before your next release.</p>
        <p class="ap-feed__stats">5 comments · 12 reactions</p>
      </div>
    </article>
    <article class="ap-feed__post" tabindex="0" aria-setsize="-1" aria-labelledby="feed-p7-t" aria-describedby="feed-p7-m feed-p7-d">
      <span class="ap-feed__avatar ap-feed__avatar--accent" aria-hidden="true">AR</span>
      <div class="ap-feed__body">
        <p class="ap-feed__meta" id="feed-p7-m"><span class="ap-feed__author">Asha Rao</span> <span aria-hidden="true">·</span> <time datetime="2026-10-02T10:05">3 days ago</time></p>
        <h4 class="ap-feed__subject" id="feed-p7-t"><a href="#feed-post-7">New guidelines for error messages</a></h4>
        <p class="ap-feed__text" id="feed-p7-d">Say what went wrong and how to fix it, in that order, next to the field. The full guide is in the writing handbook.</p>
        <p class="ap-feed__stats">7 comments · 16 reactions</p>
      </div>
    </article>
    <article class="ap-feed__post" tabindex="0" aria-setsize="-1" aria-labelledby="feed-p8-t" aria-describedby="feed-p8-m feed-p8-d">
      <span class="ap-feed__avatar ap-feed__avatar--info" aria-hidden="true">KM</span>
      <div class="ap-feed__body">
        <p class="ap-feed__meta" id="feed-p8-m"><span class="ap-feed__author">Kabir Mehta</span> <span aria-hidden="true">·</span> <time datetime="2026-10-01T17:45">4 days ago</time></p>
        <h4 class="ap-feed__subject" id="feed-p8-t"><a href="#feed-post-8">Sprint 14 retrospective notes</a></h4>
        <p class="ap-feed__text" id="feed-p8-d">Releases went more smoothly with the new checklist. We will keep code review under a day and try pairing on the hardest tickets.</p>
        <p class="ap-feed__stats">1 comment · 6 reactions</p>
      </div>
    </article>
  </template>
  <template data-ap-page>
    <article class="ap-feed__post" tabindex="0" aria-setsize="-1" aria-labelledby="feed-p9-t" aria-describedby="feed-p9-m feed-p9-d">
      <span class="ap-feed__avatar ap-feed__avatar--success" aria-hidden="true">MI</span>
      <div class="ap-feed__body">
        <p class="ap-feed__meta" id="feed-p9-m"><span class="ap-feed__author">Meera Iyer</span> <span aria-hidden="true">·</span> <time datetime="2026-09-29T09:30">Last week</time></p>
        <h4 class="ap-feed__subject" id="feed-p9-t"><a href="#feed-post-9">Welcome Nisha to the team</a></h4>
        <p class="ap-feed__text" id="feed-p9-d">Nisha Kapoor joins us from Mumbai as a content designer. Say hello in the team channel and invite her to your reviews.</p>
        <p class="ap-feed__stats">9 comments · 31 reactions</p>
      </div>
    </article>
    <article class="ap-feed__post" tabindex="0" aria-setsize="-1" aria-labelledby="feed-p10-t" aria-describedby="feed-p10-m feed-p10-d">
      <span class="ap-feed__avatar ap-feed__avatar--warning" aria-hidden="true">RS</span>
      <div class="ap-feed__body">
        <p class="ap-feed__meta" id="feed-p10-m"><span class="ap-feed__author">Ravi Shah</span> <span aria-hidden="true">·</span> <time datetime="2026-09-28T15:00">Last week</time></p>
        <h4 class="ap-feed__subject" id="feed-p10-t"><a href="#feed-post-10">Laptop requests have a new form</a></h4>
        <p class="ap-feed__text" id="feed-p10-d">Ask for new hardware through the IT form now, not by email. Requests are answered within two working days.</p>
        <p class="ap-feed__stats">No comments · 3 reactions</p>
      </div>
    </article>
    <article class="ap-feed__post" tabindex="0" aria-setsize="-1" aria-labelledby="feed-p11-t" aria-describedby="feed-p11-m feed-p11-d">
      <span class="ap-feed__avatar ap-feed__avatar--danger" aria-hidden="true">PN</span>
      <div class="ap-feed__body">
        <p class="ap-feed__meta" id="feed-p11-m"><span class="ap-feed__author">Priya Nair</span> <span aria-hidden="true">·</span> <time datetime="2026-09-28T10:40">Last week</time></p>
        <h4 class="ap-feed__subject" id="feed-p11-t"><a href="#feed-post-11">Office closed on 2 October</a></h4>
        <p class="ap-feed__text" id="feed-p11-d">The office is closed for Gandhi Jayanti on Friday 2 October. Plan deliveries and visits around it.</p>
        <p class="ap-feed__stats">2 comments · 7 reactions</p>
      </div>
    </article>
  </template>
  <template data-ap-new>
    <article class="ap-feed__post" tabindex="0" aria-setsize="-1" aria-labelledby="feed-p12-t" aria-describedby="feed-p12-m feed-p12-d">
      <span class="ap-feed__avatar ap-feed__avatar--success" aria-hidden="true">NK</span>
      <div class="ap-feed__body">
        <p class="ap-feed__meta" id="feed-p12-m"><span class="ap-feed__author">Nisha Kapoor</span> <span aria-hidden="true">·</span> <time datetime="2026-10-05T10:00">Just now</time></p>
        <h4 class="ap-feed__subject" id="feed-p12-t"><a href="#feed-post-12">Pricing page copy is ready for review</a></h4>
        <p class="ap-feed__text" id="feed-p12-d">The new pricing page copy is in the shared doc. Comments by Wednesday, please; it goes to translation on Thursday.</p>
        <p class="ap-feed__stats">No comments · 1 reaction</p>
      </div>
    </article>
    <article class="ap-feed__post" tabindex="0" aria-setsize="-1" aria-labelledby="feed-p13-t" aria-describedby="feed-p13-m feed-p13-d">
      <span class="ap-feed__avatar ap-feed__avatar--info" aria-hidden="true">AD</span>
      <div class="ap-feed__body">
        <p class="ap-feed__meta" id="feed-p13-m"><span class="ap-feed__author">Arjun Das</span> <span aria-hidden="true">·</span> <time datetime="2026-10-05T09:59">1 min ago</time></p>
        <h4 class="ap-feed__subject" id="feed-p13-t"><a href="#feed-post-13">The build is green again</a></h4>
        <p class="ap-feed__text" id="feed-p13-d">The failing contrast test was a stale snapshot. It is fixed and merged, so deploys are unblocked.</p>
        <p class="ap-feed__stats">1 comment · 4 reactions</p>
      </div>
    </article>
    <article class="ap-feed__post" tabindex="0" aria-setsize="-1" aria-labelledby="feed-p14-t" aria-describedby="feed-p14-m feed-p14-d">
      <span class="ap-feed__avatar ap-feed__avatar--success" aria-hidden="true">MI</span>
      <div class="ap-feed__body">
        <p class="ap-feed__meta" id="feed-p14-m"><span class="ap-feed__author">Meera Iyer</span> <span aria-hidden="true">·</span> <time datetime="2026-10-05T09:58">2 min ago</time></p>
        <h4 class="ap-feed__subject" id="feed-p14-t"><a href="#feed-post-14">Design review moves to 4 pm</a></h4>
        <p class="ap-feed__text" id="feed-p14-d">Today's review starts at 4 pm instead of 3, so the Bengaluru and Pune teams can both join.</p>
        <p class="ap-feed__stats">3 comments · 2 reactions</p>
      </div>
    </article>
  </template>
</div>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.3.1 Info and Relationships Level A

    role=feed holds the articles; each is named by its subject, described by its author, time and text, and placed with aria-posinset.

  • 2.1.1 Keyboard Level A

    Page Down, Page Up, Control with End or Home, and Tab reach every post and everything after the feed; loading more is a button, not a scroll position.

  • 2.2.2 Pause, Stop, Hide Level A

    New posts do not push in on their own: they wait behind a pill until someone asks to see them.

  • 2.4.3 Focus Order Level A

    Load older posts moves focus to the first post it added, so reading carries on in order, and focus never jumps when new posts arrive.

  • 2.4.11 Focus Not Obscured (Minimum) Level AA

    Scroll padding keeps a focused post clear of the pill pinned to the top of the feed.

  • 4.1.2 Name, Role, Value Level A

    aria-busy is true while a batch goes in, and aria-setsize stays -1 until the last page is in, so the count is never wrong.

  • 4.1.3 Status Messages Level AA

    Loading, how many posts arrived and the new posts waiting are status messages, announced without moving focus.

Usage

When to use it

Use it

  • Activity streams, news and social posts people read in order and keep scrolling through.
  • Lists that load in pages as people read, where a screen reader user should be able to keep reading without hunting for a link.

Use something else

  • Results people compare, sort or come back to: use pagination, which keeps their place and gives each page an address.
  • Short lists that fit at once: plain articles in a list need no feed role or extra keys.
  • A page with a footer that matters, if you were going to load on scroll: give it a Load more button instead.

Common failures

How it usually goes wrong

  • Infinite scroll above a footer

    Each time a keyboard or screen reader user gets near the footer, more posts load and push it away, so its links are never reached. A Load more button lets people choose when the list grows and reach what comes after it.

  • New posts that shove the page

    Posts inserted above the one someone is reading move it out from under them and can drag focus with it. Here they wait behind a pill that sits on a zero-height bar.

  • Focus lost after loading

    If focus stays on a button that then moves or disappears, people start again from the top. Focus goes to the first new post.

  • Articles nobody can focus

    Without tabindex on each article, Page Down has nothing to move to, and a screen reader cannot report which post it is in.

  • A set size that guesses

    aria-setsize="4" on a feed that will grow tells people the end is near when it is not. -1 says the total is unknown until the last page is in.

  • No busy state

    Without aria-busy, a screen reader can start reading a batch while half of it is still being inserted.

Notes

Building it

  • role=feed is built for a screen reader's reading mode, where posts load as people read; the keys in the table are for everyone. aria-busy asks assistive technology to wait until a batch is in.
  • The next pages wait in template elements here. In a product they come from your API, and the same function adds them, renumbers aria-posinset and, after the last page, sets aria-setsize to the real total.
  • Control with End and Home follow the Authoring Practices: they jump to the first focusable thing after or before the feed, which keeps a page footer one key away.
  • The new-posts pill sits in a zero-height sticky bar, so showing it moves nothing. It is announced once, politely, and never takes focus.
  • Here new posts arrive a few seconds after you first point at or focus the feed; in a product that is your server telling the page.

Sources: WAI-ARIA Authoring Practices: Feed · WAI-ARIA 1.2: the feed role

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction