Skip to content
weftGitHub

LoadMore

Adds the next page to the bottom of a list, by scrolling or with a button.

SourcePackage @weft-js/warpPlayground

Preview

tsx
import { derived, state } from '@weft-js/core'import { LoadMore, Row, Shape, Stack, Text } from '@weft-js/warp'export function OnPress() {  const shown = state(3)  const loading = state(false)  const load = () => {    loading.value = true    setTimeout(() => { shown.value += 3; loading.value = false }, 800)  }  const done = derived(() => shown.value >= PROJECTS.length)  return (    <Stack gap="related">      {PROJECTS.slice(0, shown.value).map(p => <ProjectRow key={p.id} name={p.name} owner={p.owner} />)}      {loading.value && <Shape kind="text" lines={2} label="Loading projects" />}      <LoadMore auto={false} loading={loading.value} done={done.value} message={done.value ? 'That is every project.' : `${shown.value} of ${PROJECTS.length}`} onLoad={load} />    </Stack>  )}type Status = 'Live' | 'Building' | 'Failed' | 'Paused'const PROJECTS: { id: string; name: string; owner: string; region: string; status: Status; deploys: number; uptime: number; latency: number; cost: number; updated: string }[] = [  { id: 'storefront', name: 'storefront', owner: 'Priya Raman', region: 'eu-west', status: 'Live', deploys: 412, uptime: 99.98, latency: 84, cost: 1240, updated: '2026-10-31' },  { id: 'checkout', name: 'checkout', owner: 'Tomás Oliveira', region: 'eu-west', status: 'Live', deploys: 288, uptime: 99.95, latency: 112, cost: 980, updated: '2026-10-31' },  { id: 'search-api', name: 'search-api', owner: 'Lena Fischer', region: 'us-east', status: 'Building', deploys: 196, uptime: 99.91, latency: 146, cost: 1610, updated: '2026-10-30' },  { id: 'billing', name: 'billing', owner: 'Mara Lindqvist', region: 'us-east', status: 'Live', deploys: 154, uptime: 99.99, latency: 73, cost: 720, updated: '2026-10-29' },  { id: 'mobile-bff', name: 'mobile-bff', owner: 'Sam Okafor', region: 'ap-south', status: 'Failed', deploys: 233, uptime: 99.42, latency: 208, cost: 860, updated: '2026-10-29' },  { id: 'notifier', name: 'notifier', owner: 'Jun Tanaka', region: 'eu-north', status: 'Live', deploys: 97, uptime: 99.97, latency: 58, cost: 310, updated: '2026-10-27' },  { id: 'reports', name: 'reports', owner: 'Noor Haddad', region: 'us-west', status: 'Paused', deploys: 41, uptime: 99.8, latency: 321, cost: 190, updated: '2026-10-22' },  { id: 'edge-cache', name: 'edge-cache', owner: 'Jun Tanaka', region: 'global', status: 'Live', deploys: 318, uptime: 100, latency: 21, cost: 1430, updated: '2026-10-31' },  { id: 'auth', name: 'auth', owner: 'Mara Lindqvist', region: 'eu-west', status: 'Live', deploys: 176, uptime: 99.99, latency: 66, cost: 540, updated: '2026-10-28' },  { id: 'ingest', name: 'ingest', owner: 'Lena Fischer', region: 'us-east', status: 'Live', deploys: 264, uptime: 99.93, latency: 131, cost: 2120, updated: '2026-10-30' },]function ProjectRow(p: { name: string; owner: string }) {  return <Row justify="between" gap="group"><Text>{p.name}</Text><Text subtle>{p.owner}</Text></Row>}

Installation

npm install @weft-js/warp

Usage

tsx
import { LoadMore } from '@weft-js/warp'

While scrolling

Loads as the end comes near. Pass root when the list scrolls in its own box.

tsx
import { derived, state } from '@weft-js/core'import { LoadMore, Row, Stack, Text } from '@weft-js/warp'export function Scrolling() {  const shown = state(6)  const loading = state(false)  let box: HTMLElement | undefined  const load = () => {    loading.value = true    setTimeout(() => { shown.value += 4; loading.value = false }, 700)  }  const done = derived(() => shown.value >= PROJECTS.length)  return (    <Stack gap="related" ref={(n: HTMLElement) => (box = n)} style="block-size:220px;overflow-y:auto">      {PROJECTS.slice(0, shown.value).map(p => <ProjectRow key={p.id} name={p.name} owner={p.owner} />)}      <LoadMore root={() => box ?? null} loading={loading.value} done={done.value} message={done.value ? 'That is every project.' : ''} onLoad={load} />    </Stack>  )}type Status = 'Live' | 'Building' | 'Failed' | 'Paused'const PROJECTS: { id: string; name: string; owner: string; region: string; status: Status; deploys: number; uptime: number; latency: number; cost: number; updated: string }[] = [  { id: 'storefront', name: 'storefront', owner: 'Priya Raman', region: 'eu-west', status: 'Live', deploys: 412, uptime: 99.98, latency: 84, cost: 1240, updated: '2026-10-31' },  { id: 'checkout', name: 'checkout', owner: 'Tomás Oliveira', region: 'eu-west', status: 'Live', deploys: 288, uptime: 99.95, latency: 112, cost: 980, updated: '2026-10-31' },  { id: 'search-api', name: 'search-api', owner: 'Lena Fischer', region: 'us-east', status: 'Building', deploys: 196, uptime: 99.91, latency: 146, cost: 1610, updated: '2026-10-30' },  { id: 'billing', name: 'billing', owner: 'Mara Lindqvist', region: 'us-east', status: 'Live', deploys: 154, uptime: 99.99, latency: 73, cost: 720, updated: '2026-10-29' },  { id: 'mobile-bff', name: 'mobile-bff', owner: 'Sam Okafor', region: 'ap-south', status: 'Failed', deploys: 233, uptime: 99.42, latency: 208, cost: 860, updated: '2026-10-29' },  { id: 'notifier', name: 'notifier', owner: 'Jun Tanaka', region: 'eu-north', status: 'Live', deploys: 97, uptime: 99.97, latency: 58, cost: 310, updated: '2026-10-27' },  { id: 'reports', name: 'reports', owner: 'Noor Haddad', region: 'us-west', status: 'Paused', deploys: 41, uptime: 99.8, latency: 321, cost: 190, updated: '2026-10-22' },  { id: 'edge-cache', name: 'edge-cache', owner: 'Jun Tanaka', region: 'global', status: 'Live', deploys: 318, uptime: 100, latency: 21, cost: 1430, updated: '2026-10-31' },  { id: 'auth', name: 'auth', owner: 'Mara Lindqvist', region: 'eu-west', status: 'Live', deploys: 176, uptime: 99.99, latency: 66, cost: 540, updated: '2026-10-28' },  { id: 'ingest', name: 'ingest', owner: 'Lena Fischer', region: 'us-east', status: 'Live', deploys: 264, uptime: 99.93, latency: 131, cost: 2120, updated: '2026-10-30' },]function ProjectRow(p: { name: string; owner: string }) {  return <Row justify="between" gap="group"><Text>{p.name}</Text><Text subtle>{p.owner}</Text></Row>}

Loading

The button stays, disabled, and the words are announced.

tsx
import { LoadMore } from '@weft-js/warp'export function Loading() {  return (    <LoadMore auto={false} loading onLoad={() => {}} />  )}

Nothing left

done swaps the button for your message.

tsx
import { LoadMore } from '@weft-js/warp'export function Done() {  return (    <LoadMore auto={false} done message="That is every deploy from October." onLoad={() => {}} />  )}

When to use

Use it for

  • A feed people read from the top down

Reach for something else

  • PaginationBar, when people need to find their way back to a place

Playground

import { LoadMore } from '@weft-js/warp'<LoadMore auto={false} message="That is every project." loading={loading.value} done={done()} onLoad={load} />

Accessibility

  • The button is always there, even with auto, so nobody depends on scrolling.
  • The message is announced as it changes.

API Reference

PropTypeDefaultDescription
loadingbooleanfalseA page is on its way. Stops further calls.
donebooleanfalseThere is nothing left. Removes the button.
messageunknown—Words beside the button, or in its place once done.
autobooleantrueLoad when the end scrolls into view.
root(() => HTMLElement | null)—The box that scrolls, if it isn't the window.
EventTypeDefaultDescription
onLoad() => void—Called when more is wanted, by a press or by reaching the end.

RelatedPagination barVirtual listFeed