Components

Infinite Scroll

Loads more content when a sentinel scrolls into view, with a load-more fallback.

php artisan blatui:add infinite-scroll
Loading more…
You’ve reached the end.
{{--
    A scrollable box that loads more items as you scroll. The component dispatches a
    'load-more' event when its sentinel nears the bottom; this demo listens for it and
    appends 5 more items from a JS array (with a short simulated delay so the spinner +
    aria-busy are visible), capping at 30 so the "end" state shows. The Load more button
    triggers the exact same path, so keyboard / no-JS users are covered too.

    Real apps replace the demo's setTimeout with a Livewire call or fetch():
        @load-more="fetch('/items?page=' + page)
            .then(r => r.json())
            .then(rows => { items.push(...rows.data); page++;
                $event.target.dispatchEvent(new CustomEvent('load-more-done',
                    { detail: { done: !rows.hasMore } })); })"
--}}
<div
    x-data="{
        items: Array.from({ length: 10 }, (_, i) => i + 1),
        max: 30,
        loadDemo(e) {
            // Simulate a network round-trip so the loading state is visible.
            setTimeout(() => {
                const start = this.items.length;
                const add = Math.min(5, this.max - start);
                for (let i = 1; i <= add; i++) this.items.push(start + i);
                const done = this.items.length >= this.max;
                e.target.dispatchEvent(new CustomEvent('load-more-done', { detail: { done } }));
            }, 600);
        },
    }"
    class="w-full max-w-md"
>
    <div class="h-72 overflow-y-auto rounded-lg border bg-card p-3">
        <x-ui.infinite-scroll :threshold="120" x-on:load-more="loadDemo($event)">
            <template x-for="n in items" :key="n">
                <div class="flex items-center gap-3 rounded-md border bg-background px-3 py-2.5 text-sm">
                    <span class="flex size-7 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground" x-text="n"></span>
                    <span class="text-foreground">Item <span x-text="n"></span></span>
                </div>
            </template>
        </x-ui.infinite-scroll>
    </div>
</div>

API Reference

Props, slots and exposed methods for <x-ui.infinite-scroll>.

Props

Prop Type Default Description
threshold int 200 Distance in pixels before the sentinel reaches the scroll edge at which loadMore() fires. Mapped to the IntersectionObserver rootMargin so loading begins a little ahead of the actual edge.

Slots

Slot Description
default Your initial items. The component appends a loading indicator, a "Load more" button (keyboard / no-JS fallback) and an invisible sentinel after the slot content.

Methods

Available on the component's Alpine scope — call them from markup in the slot (e.g. @click="…").

Method Description
loadMore() Sets the loading state and dispatches a cancelable "load-more" event. Listen for it (Livewire / fetch) to append rows, then call detail.loaded() / detail.done() or dispatch "load-more-done" on the root. No-op while already loading or finished.
loaded() Clears the loading state after a batch of items has been appended, re-arming the observer for the next page.
finish() Marks the list complete: hides the sentinel and Load more button and shows the end-of-list message.