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
onLoadMore when the sentinel scrolls into view.rootMargin before the sentinel is fully visible.loading is true.hasMore is false.Loader and EndMessage render automatically based on state.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
Anatomy
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
| Prop | Type | Default | Description |
|---|---|---|---|
onLoadMore | () => void | required | Called when the sentinel scrolls into view and more data can be loaded |
hasMore | boolean | true | Whether there are more items to load |
loading | boolean | false | Whether a load is in flight (suppresses further triggers) |
rootMargin | string | '0px' | Margin around the root used to pre-fetch before the sentinel is fully visible |
disabled | boolean | false | Disable 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
| Attribute | Element | Values |
|---|---|---|
data-loading | Root | Present while loading |
data-has-more | Root | Present while more items can load |
data-infinite-scroll-sentinel | Sentinel | Always present |
Accessibility
- The
Sentinelisaria-hiddensince it is a non-visual scroll trigger. - The
Loaderusesrole="status"witharia-live="polite"so screen readers announce when more content is being fetched.