Load more
FreeThe end of a long list: the next page loads as it comes into view, failures offer a retry, a button does the same for keyboards, and the end says so.
pnpm dlx shadcn@latest add https://caliperui.site/r/load-more.json- Card payment 001−€12.00
- Card payment 002−€49.00
- Card payment 003−€86.00
- Card payment 004−€123.00
- Card payment 005−€160.00
- Card payment 006−€17.00
- Card payment 007−€54.00
- Card payment 008−€91.00
- Card payment 009−€128.00
- Card payment 010−€165.00
- Card payment 011−€22.00
- Card payment 012−€59.00
When to use #
Use it for
- Feeds, search results and galleries.
- Lists where pages are an implementation detail.
Skip it when
- Tables people jump around in; use pagination.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/load-more.json
Adds its 3 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above.
import { useState } from "react" import { Checkbox } from "@/components/ui/checkbox" import { LoadMore } from "@/components/ui/load-more" const wait = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)); export function LoadMoreDemo() { const [rows, setRows] = useState(12); const [failNext, setFailNext] = useState(false); return ( <div className="grid w-full max-w-md gap-3"> <label className="flex items-center gap-2 text-sm"><Checkbox checked={failNext} onChange={(event) => setFailNext(event.target.checked)} />Fail the next page</label> <div className="h-[340px] overflow-y-auto rounded-[14px] shadow-[inset_0_0_0_1px_var(--border)]" tabIndex={0} role="region" aria-label="Transactions"> <ul className="grid"> {Array.from({ length: rows }, (_, index) => ( <li key={index} className="flex justify-between border-b border-border px-4 py-3 text-sm"> <span>Card payment {String(index + 1).padStart(3, "0")}</span> <span className="font-mono text-muted-foreground">−€{(12 + ((index * 37) % 180)).toFixed(2)}</span> </li> ))} </ul> <LoadMore hasMore={rows < 48} onLoad={async () => { await wait(900); if (failNext) { setFailNext(false); throw new Error("Network"); } setRows((n) => n + 12); }} endLabel="That is every transaction this month" /> </div> </div> ); }
API reference #
LoadMore
component
Accessibility #
It exposes status and alert semantics, with aria-hidden kept in step with state. Changes that matter are announced through a live region.
No custom key handling; Tab and the native activation keys do the work.
Motion #
It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.