Özer

Load More

Buttons 18

About

A load-more button with automatic infinite-scroll via IntersectionObserver, animated idle/loading/error/end states, and a bounded auto-load limit before it hands control back to the user.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Load More to your project and fits it to your theme.

Code

How Load More is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import { LoadMore } from "@/components/ui/load-more";
import { useEffect, useRef, useState } from "react";
import { motion } from "motion/react";

const TOTAL = 40;
const PAGE = 8;
const EASE = [0.23, 1, 0.32, 1] as const;

export default function LoadMoreDemo() {
  const [count, setCount] = useState(PAGE);
  const scroller = useRef<HTMLDivElement>(null);
  const loaded = useRef(PAGE);
  const timers = useRef(new Set<ReturnType<typeof setTimeout>>());

  useEffect(() => {
    const running = timers.current;
    return () => {
      running.forEach(clearTimeout);
      running.clear();
    };
  }, []);

  const load = () =>
    new Promise<boolean>((resolve) => {
      const next = Math.min(TOTAL, loaded.current + PAGE);
      const id = setTimeout(() => {
        timers.current.delete(id);
        loaded.current = next;
        setCount(next);
        resolve(next < TOTAL);
      }, 700);
      timers.current.add(id);
    });

  return (
    <div
      ref={scroller}
      className="mx-auto h-[360px] w-full max-w-sm overflow-y-auto overscroll-contain rounded-[14px] border border-stone-200 bg-white p-3 dark:border-white/[0.16] dark:bg-[#1d1d1a]"
    >
      <ul className="space-y-1">
        {Array.from({ length: count }, (_, i) => (
          <motion.li
            key={i}
            initial={i < PAGE ? false : { opacity: 0, y: 8 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{
              duration: 0.26,
              ease: EASE,
              delay: (i % PAGE) * 0.03,
            }}
            className="mat-panel rounded-[9px] px-3 py-2.5 text-[12.5px] text-ink"
          >
            Item {String(i + 1).padStart(2, "0")}
          </motion.li>
        ))}
      </ul>

      <div className="pt-3">
        <LoadMore
          onLoad={load}
          hasMore={count < TOTAL}
          rootRef={scroller}
          rootMargin="120px 0px"
          maxAutoLoads={2}
        />
      </div>
    </div>
  );
}

Dependencies

More by Özer

View all
  • Password Strength

    Password Strength

    1.3K
  • OTP Input

    OTP Input

    802
  • Loading Button

    Loading Button

    601
  • Progress Bar

    Progress Bar

    588
  • Reorder List

    Reorder List

    310
  • Task Steps

    Task Steps

    276
  • Hold to Confirm

    Hold to Confirm

    250
  • Reading Progress

    Reading Progress

    169

More in Buttons

View all
  • Button

    Button

    14
  • Button

    Button

    34
  • Texture Button

    Texture Button

    92
  • Button Save

    Button Save

    46
  • Button

    Button

    64
  • Button Group

    Button Group

    3
  • App Store Button

    App Store Button

    454
  • Notification Button

    Notification Button

    103

Explore more

Part of interior.dev — browse the full library on 21st.dev.