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.

Create a free account to save this page, take notes and track your progress. Sign in

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:

A Load more buttonTry it yourself
<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:

Infinite scroll in a scrolling boxTry it yourself
<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?

  1. revealed
  2. load
  3. scroll
  4. every 1s
Show answer

Answer: revealed

revealed fires once the element becomes visible in the viewport; intersect works inside scroll containers.

Was this page helpful?