Componentnavigation
InfiniteScroll
IntersectionObserver sentinel: when visible calls onLoadMore, shows animated loader row; wraps children.
Preview
Live Preview
Item 1
Item 2
Item 3
Item 4
Item 5
Loading more items...
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add infinite-scrollOr install the full npm package:
npm install bigbulluiUsage
import { InfiniteScroll } from "@/components/ui/infinite-scroll"
<InfiniteScroll onLoadMore={() => {}} hasMore={true}>
{[1, 2, 3, 4, 5].map((i) => <div key={i} className="p-3 bg-card">Item {i}</div>)}
</InfiniteScroll>Source code
infinite-scroll.tsxZero dependencies (only React + utils). Copy and paste directly into your project:
"use client";
import * as React from "react";
import { cn } from "./lib/utils";
export type InfiniteScrollProps = {
onLoadMore: () => void;
hasMore: boolean;
loader?: React.ReactNode;
children?: React.ReactNode;
};
export function InfiniteScroll({ onLoadMore, hasMore, loader, children }: InfiniteScrollProps) {
const sentinelRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
onLoadMore();
}
},
{ rootMargin: "100px" },
);
if (sentinelRef.current) {
observer.observe(sentinelRef.current);
}
return () => observer.disconnect();
}, [onLoadMore]);
return (
<div className="relative">
{children}
{hasMore && (
<div
ref={sentinelRef}
className={cn(
"py-6 text-center",
"motion-reduce:animate-none",
)}
>
{loader || (
<span className="text-sm font-mono uppercase tracking-[0.15em] text-muted-foreground">
Loading more...
</span>
)}
</div>
)}
</div>
);
}Props
| Prop | Type | Description |
|---|---|---|
| onLoadMore | () => void | Callback when sentinel intersects. |
| hasMore | boolean | Whether more items are available. |
| loader | React.ReactNode | Optional custom loader component. |