Skip to content
ARC UI ARC Reactive Components
Docs Components Tokens Synthesizer
v2.1.0
Getting Started Frameworks Design Tokens Theming Theme Synthesizer Accessibility Browser Support Changelog Contributing All Components App ShellAspect GridAuth ShellCenterClusterContainerDashboard GridDockFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkSpeed DialStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCalloutCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageInfinite ScrollMarqueeScroll AreaSeparatorSkeletonSpinnerStackVirtual List Animated NumberBadgeComparisonCountdown TimerData TableDescription ListDiffKey ValueListMeterSparklineStatStepperTableTagTimelineValue Card BlockquoteCode BlockGradient TextHighlightKbdMarkdownNumber FormatProseTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerFieldsetFile UploadFormHotkeyIcon ButtonInputInput GroupLabelMulti SelectNumber InputOTP InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSliderSortable ListSwitch GroupTextareaTheme ToggleTime PickerToggle AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuDialogDropdown MenuGuided TourHover CardInline MessageLoading OverlayModalNotification PanelPopoverProgressProgress ToastSheetSnackbarSpotlightToastTooltip
Components Infinite Scroll
content interactive
<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

See Also