<arc-infinite-scroll> Overview
Guidelines
When to use
- Set `loading` to `true` immediately when you begin fetching data, and reset it to `false` when data arrives
- Set `finished` to `true` when the API indicates no more pages remain, so the observer disconnects
- Use a reasonable `threshold` (100-300px) so content loads before users hit the bottom and see a gap
- Place Infinite Scroll inside a scrollable parent or let the page itself be the scroll container
- Append new items as direct children of the component -- they render in the default slot
When not to use
- Do not leave `loading` as `true` indefinitely -- this blocks further load events and leaves the spinner visible
- Do not use Infinite Scroll for small, fixed datasets -- standard pagination or a simple list is more appropriate
- Do not set `threshold` to 0 -- the user will see a flash of empty space before content loads
- Do not nest multiple Infinite Scroll components -- their observers may conflict
- Avoid using Infinite Scroll without providing a way to reach footer content, since it pushes the footer down continuously
Features
- Intersection Observer-based sentinel that fires `arc-load-more` when the user nears the content bottom
- Configurable `threshold` prop (in pixels) to control how far in advance the load event triggers
- Built-in `arc-spinner` displayed in the footer when `loading` is `true`
- Automatic suppression of duplicate load events while loading is in progress
- End-of-list state via `finished` prop -- removes the sentinel and shows "No more items" text
- ARIA `role="feed"` and `aria-busy` for accessible loading communication
- Observer automatically disconnects and reconnects when `finished` or `disabled` change
- Disabled state at 40% opacity with pointer events blocked
Preview
Usage
This component requires JavaScript. No pure HTML/CSS version is available — use the Web Component directly or a framework wrapper.
<arc-infinite-scroll id="feed" threshold="200">
<!-- Items appended here -->
</arc-infinite-scroll>
<script>
const feed = document.getElementById('feed');
let page = 1;
feed.addEventListener('arc-load-more', async () => {
feed.loading = true;
const items = await fetchPage(page++);
items.forEach(item => feed.appendChild(createItemEl(item)));
feed.loading = false;
if (!items.length) feed.finished = true;
});
</script> import { InfiniteScroll } from '@arclux/arc-ui-react';
const [items, setItems] = useState(initialItems);
const [loading, setLoading] = useState(false);
const [finished, setFinished] = useState(false);
async function loadMore() {
setLoading(true);
const next = await fetchNextPage();
setItems(prev => [...prev, ...next]);
setLoading(false);
if (!next.length) setFinished(true);
}
<InfiniteScroll loading={loading} finished={finished} onArcLoadMore={loadMore}>
{items.map(item => <div key={item.id}>{item.name}</div>)}
</InfiniteScroll> <script setup>
import { ref } from 'vue';
import { InfiniteScroll } from '@arclux/arc-ui-vue';
const items = ref(initialItems);
const loading = ref(false);
const finished = ref(false);
async function loadMore() {
loading.value = true;
const next = await fetchNextPage();
items.value.push(...next);
loading.value = false;
if (!next.length) finished.value = true;
}
</script>
<template>
<InfiniteScroll :loading="loading" :finished="finished" @arc-load-more="loadMore">
<div v-for="item in items" :key="item.id">{{ item.name }}</div>
</InfiniteScroll>
</template> <script>
import { InfiniteScroll } from '@arclux/arc-ui-svelte';
let items = $state(initialItems);
let loading = $state(false);
let finished = $state(false);
async function loadMore() {
loading = true;
const next = await fetchNextPage();
items = [...items, ...next];
loading = false;
if (!next.length) finished = true;
}
</script>
<InfiniteScroll {loading} {finished} on:arc-load-more={loadMore}>
{#each items as item (item.id)}
<div>{item.name}</div>
{/each}
</InfiniteScroll> import { Component } from '@angular/core';
import { InfiniteScroll } from '@arclux/arc-ui-angular';
@Component({
imports: [InfiniteScroll],
template: `
<InfiniteScroll
[loading]="loading"
[finished]="finished"
(arc-load-more)="loadMore()"
>
<div *ngFor="let item of items">{{ item.name }}</div>
</InfiniteScroll>
`,
})
export class FeedComponent {
items = [];
loading = false;
finished = false;
async loadMore() {
this.loading = true;
const next = await this.fetchNextPage();
this.items.push(...next);
this.loading = false;
if (!next.length) this.finished = true;
}
} import { createSignal, For } from 'solid-js';
import { InfiniteScroll } from '@arclux/arc-ui-solid';
const [items, setItems] = createSignal(initialItems);
const [loading, setLoading] = createSignal(false);
const [finished, setFinished] = createSignal(false);
async function loadMore() {
setLoading(true);
const next = await fetchNextPage();
setItems(prev => [...prev, ...next]);
setLoading(false);
if (!next.length) setFinished(true);
}
<InfiniteScroll loading={loading()} finished={finished()} onArcLoadMore={loadMore}>
<For each={items()}>{item => <div>{item.name}</div>}</For>
</InfiniteScroll> import { useState } from 'preact/hooks';
import { InfiniteScroll } from '@arclux/arc-ui-preact';
const [items, setItems] = useState(initialItems);
const [loading, setLoading] = useState(false);
const [finished, setFinished] = useState(false);
async function loadMore() {
setLoading(true);
const next = await fetchNextPage();
setItems(prev => [...prev, ...next]);
setLoading(false);
if (!next.length) setFinished(true);
}
<InfiniteScroll loading={loading} finished={finished} onArcLoadMore={loadMore}>
{items.map(item => <div key={item.id}>{item.name}</div>)}
</InfiniteScroll> API
| Prop | Type | Default | Description |
|---|---|---|---|
threshold | number | 200 | Distance in pixels from the bottom of the content at which `arc-load-more` fires. Controls how eagerly new data is requested. |
loading | boolean | false | When true, displays a spinner in the footer and suppresses additional `arc-load-more` events. |
finished | boolean | false | When true, disconnects the observer and displays "No more items" text in the footer. |
disabled | boolean | false | Disables the component, disconnects the observer, and reduces opacity to 40%. |
Events
| Event | Description |
|---|---|
arc-load-more | Fired when the scroll sentinel enters the viewport, signaling more content should load |