About
A bias-based infinite scroll wrapper and hook that manages pagination and masks child items for progressive "load more" lists.
Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Infinite List to your project and fits it to your theme.
Code
How Infinite List is used: the demo's code. The component's own source comes with a free account.
Usage.tsx
'use client'
import { MaskedList, useInfiniteList } from '@/components/ui/infinite-list'
import Image from 'next/image'
import { useEffect, useState, useTransition } from 'react'
import { Button } from '@/components/ui/button'
import { Card, CardFooter } from '@/components/ui/card'
type Pokemon = { name: string; url: string; image: string }
async function fetchPokemon(offset: number, limit: number): Promise<Pokemon[]> {
const res = await fetch(
`https://pokeapi.co/api/v2/pokemon?offset=${offset}&limit=${limit}`
).then((r) => r.json())
return Promise.all(
res.results.map(async (p: { url: string }) => {
const data = await fetch(p.url).then((r) => r.json())
return { name: data.name, url: p.url, image: data.sprites.front_default }
})
)
}
export default function InfiniteListDemo() {
const pageSize = 12
const [pokemon, setPokemon] = useState<Pokemon[]>([])
const [isPending, startTransition] = useTransition()
const list = useInfiniteList({ pageSize, initialItems: pokemon })
useEffect(() => {
fetchPokemon(0, pageSize * 2).then(setPokemon)
}, [])
const loadMore = () => {
list.nextPage()
startTransition(async () => {
const newPokemon = await fetchPokemon(list.offset, pageSize)
setPokemon((prev) => [...prev, ...newPokemon])
})
}
return (
<div className="relative h-[500px] w-full overflow-auto">
<div className="bg-background/90 border-border sticky top-4 right-8 z-10 ml-auto w-fit rounded-lg border px-3 py-2 font-mono text-xs">
DISPLAYED: {Math.min(list.displayLimit, pokemon.length)} | LOADED:{' '}
{pokemon.length}
</div>
<div className="flex flex-col gap-6 p-6">
<h2 className="text-2xl font-semibold">Pokemon Infinite List</h2>
<div className="mb-6 grid grid-cols-2 gap-3 md:grid-cols-3">
<MaskedList {...list}>
{pokemon.map((p) => (
<Card key={p.url} className="gap-0 overflow-hidden p-0">
<div className="bg-muted aspect-square">
<Image
src={p.image}
alt={p.name}
width={500}
height={500}
className="h-full w-full object-cover [image-rendering:pixelated]"
unoptimized
/>
</div>
<CardFooter className="p-4">
<h3 className="text-base font-semibold capitalize">
{p.name}
</h3>
</CardFooter>
</Card>
))}
</MaskedList>
</div>
<Button
onClick={loadMore}
disabled={isPending}
variant="outline"
className="self-center shadow-none transition-none"
>
{isPending ? 'Loading...' : 'Load More'}
</Button>
</div>
</div>
)
}















