htmx Tutorial · Common Patterns · Lesson 17 of 30
Load More and Infinite Scroll
Load the next page of results with a button, or automatically when the user scrolls to the end.
Load more
The last item of each page contains a button that fetches the next page and replaces itself with it
(hx-swap="outerHTML"). The new page ends with a new button for the page after, until there's no more:
<ul id="posts">
<li>Post 1</li><li>Post 2</li><li>Post 3</li>
<li><button hx-get="/posts?page=2" hx-target="closest li" hx-swap="outerHTML">Load more</button></li>
</ul>
<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
server.get("/posts", (req) => {
const page = Number(req.query.page);
const items = [1, 2, 3].map((i) => `<li>Post ${(page - 1) * 3 + i}</li>`).join("");
const more = page < 4
? `<li><button hx-get="/posts?page=${page + 1}" hx-target="closest li" hx-swap="outerHTML">Load more</button></li>`
: "<li><em>That's everything.</em></li>";
return items + more;
});
</script>
Infinite scroll
Change the trigger to revealed: the request fires when the element scrolls into view. Put it on the last item
so the next page loads just before the user reaches the end:
<div style="height:180px;overflow:auto;border:1px solid #ccc">
<p>Photo 1</p><p>Photo 2</p><p>Photo 3</p><p>Photo 4</p><p>Photo 5</p>
<p hx-get="/photos?page=2" hx-trigger="intersect once" hx-swap="outerHTML">Loading more photos...</p>
</div>
<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
server.get("/photos", (req) => {
const page = Number(req.query.page);
let html = "";
for (let i = 1; i <= 5; i++) html += `<p>Photo ${(page - 1) * 5 + i}</p>`;
return html + (page < 5 ? `<p hx-get="/photos?page=${page + 1}" hx-trigger="intersect once" hx-swap="outerHTML">Loading more photos...</p>`
: "<p><em>No more photos.</em></p>");
}, { delay: 500 });
</script>
Tips:
- Keep a real "Load more" button or links for users who can't scroll (keyboard, screen readers) and for search engines.
- Inside a scrolling container (not the page), use
hx-trigger="intersect once", which works with any scroll parent.
Test yourself
Which trigger loads the next page when the last element scrolls into view on the page?
revealedloadscrollevery 1s
Show answer
Answer: revealed
revealed fires once the element becomes visible in the viewport; intersect works inside scroll containers.