Skip to content

Navigation

Feed

An infinite-scroll list of articles that keyboard users can page through and escape.

When to use it: Use it for scroll-loaded content such as news or activity streams; use GridList for a selectable collection.

On this page

Example

Roasted tomato soup

A weeknight classic, deepened with slow-roasted garlic.

Charred corn salad

Sweet corn, lime, and a little heat from pickled jalapeño.

Smoky bean stew

Pantry beans simmered with smoked paprika and greens.

Feed.tsxtsx
import { Feed, FeedArticle } from "@comp0/react";

const stories = [
  {
    id: "feed-story-soup",
    title: "Roasted tomato soup",
    teaser: "A weeknight classic, deepened with slow-roasted garlic.",
  },
  {
    id: "feed-story-salad",
    title: "Charred corn salad",
    teaser: "Sweet corn, lime, and a little heat from pickled jalapeño.",
  },
  {
    id: "feed-story-stew",
    title: "Smoky bean stew",
    teaser: "Pantry beans simmered with smoked paprika and greens.",
  },
];

export function Example() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-2">
      <button
        type="button"
        className="self-start rounded px-3 py-2.5 text-base text-teal-700 hover:bg-teal-50 sm:py-2 sm:text-sm dark:text-teal-300 dark:hover:bg-teal-950"
      >
        Refresh stories
      </button>
      <Feed aria-label="Recipe stories" total={12} className="flex flex-col gap-2">
        {stories.map((story) => (
          <FeedArticle
            key={story.id}
            aria-labelledby={story.id}
            className="rounded border border-zinc-950/10 p-3 outline-teal-600 focus-visible:outline-2 dark:border-white/10 dark:outline-teal-400"
          >
            <h3
              id={story.id}
              className="text-base font-medium text-zinc-900 sm:text-sm dark:text-zinc-100"
            >
              {story.title}
            </h3>
            <p className="mt-1 text-base text-zinc-600 sm:text-sm dark:text-zinc-400">
              {story.teaser}
            </p>
          </FeedArticle>
        ))}
      </Feed>
      <button
        type="button"
        className="self-start rounded px-3 py-2.5 text-base text-teal-700 hover:bg-teal-50 sm:py-2 sm:text-sm dark:text-teal-300 dark:hover:bg-teal-950"
      >
        Load older stories
      </button>
    </div>
  );
}

Anatomy

Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.

A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.

  1. Feed

    The scroll-loaded article list; PageDown and PageUp walk it and Ctrl+Home / Ctrl+End escape it. Owns a DOM element.

  2. FeedArticle

    Native article that can receive focus; announces its position and set size automatically. Owns a DOM element.

Step by step

  1. 1

    Add the main part

    Wrap the stream in Feed with an aria-label.

  2. 2

    Add the supporting parts

    Render one FeedArticle per story with aria-labelledby pointing at its title.

  3. 3

    Make the behavior clear

    Set busy while loading more, and total when you know how many articles exist beyond the rendered ones.

    Exampletsx
    <Feed aria-label="Recipe stories" busy={loading}><FeedArticle aria-labelledby="story-1"><h3 id="story-1">Roasted tomato soup</h3></FeedArticle></Feed>

Keyboard

PgDn
Moves focus to the next article.
PgUp
Moves focus to the previous article.
CtrlEnd
Moves focus to the first focusable element after the feed.
CtrlHome
Moves focus to the first focusable element before the feed.
Moves through links and controls inside the focused article.

Forms and accessibility

A feed does not create form values.

Accessibility checklist

  • Give the feed an aria-label naming the stream, such as Recipe stories.
  • Label every article via aria-labelledby on FeedArticle so PageDown announces where it landed.
  • Keep something focusable before and after the feed; Ctrl+Home and Ctrl+End are how keyboard users escape an endless scroll.

API reference

Importtsx
import { Feed, FeedArticle } from "@comp0/react";

Feed

DOM element

The scroll-loaded article list; PageDown and PageUp walk it and Ctrl+Home / Ctrl+End escape it.

PropTypeDescription
aria-labelstringNames the feed for assistive technology; required.
busybooleanMarks the feed as loading more articles via aria-busy.
totalnumberTotal article count when known beyond the rendered ones; feeds aria-setsize, which otherwise reports the rendered count.

FeedArticle

DOM element

Native article that can receive focus; announces its position and set size automatically.

PropTypeDescription
aria-labelledbystringPoint it at the article's title element so screen readers hear what it is about.

Style hooks

Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.

[data-busy]

on Feed

More articles are loading.
:focus-visible

on FeedArticle

The article has keyboard focus.

Keep exploring