Skip to Content
⭐️ Leave a star →
ComponentsInfiniteScroll

InfiniteScroll

A sentinel-based load-more primitive built on an intersection observer. Render your list, then drop a Sentinel at the end — it calls onLoadMore as it scrolls into view, gated by hasMore and loading.

Features

Triggers onLoadMore when the sentinel scrolls into view.
Pre-fetches early via rootMargin before the sentinel is fully visible.
Suppresses duplicate triggers while loading is true.
Stops triggering once hasMore is false.
Loader and EndMessage render automatically based on state.
Can be disabled entirely with disabled.

Example

  • Item #1
  • Item #2
  • Item #3
  • Item #4
  • Item #5
  • Item #6
  • Item #7
  • Item #8
  • Item #9
  • Item #10
  • Item #11
  • Item #12
  • Item #13
  • Item #14
  • Item #15
import { InfiniteScroll } from '@wire-ui/react' <InfiniteScroll.Root onLoadMore={loadMore} hasMore={hasMore} loading={loading}> <ul> {items.map((i) => <li key={i}>Item #{i + 1}</li>)} </ul> <InfiniteScroll.Loader>Loading more…</InfiniteScroll.Loader> <InfiniteScroll.EndMessage>You've reached the end</InfiniteScroll.EndMessage> <InfiniteScroll.Sentinel /> </InfiniteScroll.Root>

Styling

InfiniteScroll ships zero layout styles — give the Root a fixed height and overflow-auto to scroll, or omit the height to use the window/page as the scroll container. The Root exposes data-loading and data-has-more so you can style based on state. Loader renders only while loading is true; EndMessage renders only when hasMore is false.

<InfiniteScroll.Root className=" h-80 overflow-auto data-[loading]:opacity-90 "> </InfiniteScroll.Root>

Using data attributes

The Root sets data-loading (present while a load is in flight) and data-has-more (present while more items can load). The Sentinel sets data-infinite-scroll-sentinel.

/* Dim the list while fetching */ [data-loading] { opacity: 0.9; } /* Hide the load-more affordance at the end */ :not([data-has-more]) .load-more { display: none; }

Root props

PropTypeDefaultDescription
onLoadMore() => voidrequiredCalled when the sentinel scrolls into view and more data can be loaded
hasMorebooleantrueWhether there are more items to load
loadingbooleanfalseWhether a load is in flight (suppresses further triggers)
rootMarginstring'0px'Margin around the root used to pre-fetch before the sentinel is fully visible
disabledbooleanfalseDisable triggering entirely

Root also accepts all standard <div> HTML attributes.

Sentinel props

The Sentinel renders an aria-hidden <div> observed by the intersection observer. It accepts all standard <div> HTML attributes. Place it as the last child of the Root.

Loader props

The Loader renders a <div role="status" aria-live="polite"> only while loading is true. It accepts all standard <div> HTML attributes.

EndMessage props

The EndMessage renders a <div> only when hasMore is false. It accepts all standard <div> HTML attributes.

Data attributes

AttributeElementValues
data-loadingRootPresent while loading
data-has-moreRootPresent while more items can load
data-infinite-scroll-sentinelSentinelAlways present

Accessibility

  • The Sentinel is aria-hidden since it is a non-visual scroll trigger.
  • The Loader uses role="status" with aria-live="polite" so screen readers announce when more content is being fetched.
Last updated on

MIT License © 2026 wire-ui

InfiniteScroll – Wire UI