Skip to main content
2.0.16
Light Dark System
Compact Comfortable

Infinite Scroll

<zn-infinite-scroll> ZnInfiniteScroll
Since 2.0 experimental

Loads further pages of server-rendered content as the user scrolls to the end.

Wrap the first page of any server-rendered list. When the end of the content comes within one screen of view, the next page is fetched from uri (with #page# replaced by the page number) and its HTML is appended. Loading stops at the first empty response. With no content inside, it starts by loading page 1.

<div style="height: 300px; overflow: auto;">
  <zn-infinite-scroll uri="/assets/infinite-scroll/page-#page#.html">
    <zn-tile caption="Item 1" description="Rendered with the page"></zn-tile>
    <zn-tile caption="Item 2" description="Rendered with the page"></zn-tile>
    <zn-tile caption="Item 3" description="Rendered with the page"></zn-tile>
    <zn-tile caption="Item 4" description="Rendered with the page"></zn-tile>
    <zn-tile caption="Item 5" description="Rendered with the page"></zn-tile>
  </zn-infinite-scroll>
</div>

Examples

Starting Empty

Leave the component empty and it loads the first page itself.

<div style="height: 300px; overflow: auto;">
  <zn-infinite-scroll uri="/assets/infinite-scroll/page-#page#.html"></zn-infinite-scroll>
</div>

Tile Grid

Lay the component out as a grid and each loaded page flows into it. Content in the loader slot is shown while a page loads and can line up with the grid’s columns using subgrid, so placeholder tiles stand in for the next row.

<div class="tile-feed">
  <zn-infinite-scroll uri="/assets/infinite-scroll/tiles-#page#.html">
    <zn-tile caption="Q3 revenue report" description="Report · Updated 2h ago" icon="bar_chart" icon-color="success"></zn-tile>
    <zn-tile caption="Spring campaign" description="Campaign · 12 assets" icon="campaign" icon-color="ai"></zn-tile>
    <zn-tile caption="Supplier invoices" description="Folder · 48 files" icon="receipt_long" icon-color="warning"></zn-tile>
    <zn-tile caption="Onboarding checklist" description="Template · Shared" icon="checklist" icon-color="info"></zn-tile>
    <zn-tile caption="Churned accounts" description="Segment · 312 customers" icon="person_remove" icon-color="danger"></zn-tile>
    <zn-tile caption="Brand guidelines" description="Document · v4" icon="palette" icon-color="neutral"></zn-tile>
    <zn-tile caption="Weekly standup" description="Event · Mondays 9:30" icon="event" icon-color="info"></zn-tile>
    <zn-tile caption="Product photos" description="Folder · 126 images" icon="photo_library" icon-color="ai"></zn-tile>
    <zn-tile caption="Refund policy" description="Document · Legal review" icon="gavel" icon-color="warning"></zn-tile>
    <zn-tile caption="Renewal reminders" description="Automation · Active" icon="autorenew" icon-color="success"></zn-tile>
    <zn-tile caption="Failed payments" description="Report · 18 this week" icon="credit_card_off" icon-color="danger"></zn-tile>
    <zn-tile caption="Warehouse stock" description="Inventory · 2,410 items" icon="inventory_2" icon-color="neutral"></zn-tile>
    <div slot="loader" class="tile-skeleton-row">
      <zn-tile loading icon></zn-tile>
      <zn-tile loading icon></zn-tile>
      <zn-tile loading icon></zn-tile>
      <zn-tile loading icon></zn-tile>
    </div>
  </zn-infinite-scroll>
</div>

<style>
  /* Padding keeps tile borders and hover shadows clear of the scroll edges and scrollbar */
  .tile-feed {
    height: 360px;
    overflow: auto;
    padding: var(--zn-spacing-x-small);
  }

  /* At most 4 columns, so each page of 12 tiles fills whole rows */
  .tile-feed zn-infinite-scroll {
    --gap: var(--zn-spacing-small);
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(max(240px, calc((100% - 3 * var(--gap)) / 4)), 1fr));
    gap: var(--gap);
  }

  .tile-feed zn-tile {
    background: rgb(var(--zn-panel));
    border: 1px solid rgb(var(--zn-border-color));
    border-radius: var(--zn-border-radius-large);
  }

  .tile-feed zn-tile:not([loading]) {
    transition: border-color 150ms ease, box-shadow 150ms ease;
  }

  .tile-feed zn-tile:not([loading]):hover {
    border-color: rgb(var(--zn-primary));
    box-shadow: 0 2px 8px rgb(0 0 0 / 6%);
  }

  /* One row of placeholders on the grid's columns; any that don't fit collapse out of sight */
  .tile-feed .tile-skeleton-row {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: subgrid;
    grid-template-rows: auto;
    grid-auto-rows: 0;
    row-gap: 0;
    align-items: start;
    overflow: hidden;
  }
</style>

Loading State

While a page is loading, the loader slot is shown after the content. These examples request a page that never arrives, so the loading state stays on screen.

With no loader slot, a skeleton bar is shown.

<zn-infinite-scroll uri="/assets/infinite-scroll/pending-#page#.html">
  <zn-tile caption="Item 1" description="Rendered with the page"></zn-tile>
  <zn-tile caption="Item 2" description="Rendered with the page"></zn-tile>
  <zn-tile caption="Item 3" description="Rendered with the page"></zn-tile>
</zn-infinite-scroll>

In the tile grid, a row of placeholder tiles stands in for the next page. This uses the styles from the tile grid example.

<div class="tile-feed" style="height: auto;">
  <zn-infinite-scroll uri="/assets/infinite-scroll/pending-#page#.html">
    <zn-tile caption="Q3 revenue report" description="Report · Updated 2h ago" icon="bar_chart" icon-color="success"></zn-tile>
    <zn-tile caption="Spring campaign" description="Campaign · 12 assets" icon="campaign" icon-color="ai"></zn-tile>
    <zn-tile caption="Supplier invoices" description="Folder · 48 files" icon="receipt_long" icon-color="warning"></zn-tile>
    <zn-tile caption="Onboarding checklist" description="Template · Shared" icon="checklist" icon-color="info"></zn-tile>
    <zn-tile caption="Churned accounts" description="Segment · 312 customers" icon="person_remove" icon-color="danger"></zn-tile>
    <zn-tile caption="Brand guidelines" description="Document · v4" icon="palette" icon-color="neutral"></zn-tile>
    <zn-tile caption="Weekly standup" description="Event · Mondays 9:30" icon="event" icon-color="info"></zn-tile>
    <zn-tile caption="Product photos" description="Folder · 126 images" icon="photo_library" icon-color="ai"></zn-tile>
    <zn-tile caption="Refund policy" description="Document · Legal review" icon="gavel" icon-color="warning"></zn-tile>
    <zn-tile caption="Renewal reminders" description="Automation · Active" icon="autorenew" icon-color="success"></zn-tile>
    <zn-tile caption="Failed payments" description="Report · 18 this week" icon="credit_card_off" icon-color="danger"></zn-tile>
    <zn-tile caption="Warehouse stock" description="Inventory · 2,410 items" icon="inventory_2" icon-color="neutral"></zn-tile>
    <div slot="loader" class="tile-skeleton-row">
      <zn-tile loading icon></zn-tile>
      <zn-tile loading icon></zn-tile>
      <zn-tile loading icon></zn-tile>
      <zn-tile loading icon></zn-tile>
    </div>
  </zn-infinite-scroll>
</div>

Starting Page

Set page to the last page the server already rendered. The next request is for page + 1.

<zn-infinite-scroll uri="/notes?page=#page#" page="3">
  <!-- pages 1 to 3 -->
</zn-infinite-scroll>

Server Responses

Each request is a GET with the x-kx-inline: inline header, and should return just the items for that page.

  • Items: appended to the end, then zn-load is emitted.
  • Empty body: loading stops, the finished attribute is set and zn-finish is emitted.
  • Error status or network failure: loading stops, the finished attribute is set and zn-error is emitted.

Scroll Containers

Inside a scrolling element, a screen is that element’s height. While a page loads, scrolling that reaches the end stays in the element instead of moving the page. Once loading has finished for good, scrolling past the end moves the page as usual.

Importing

If you’re using the autoloader or the traditional loader, you can ignore this section. Otherwise, feel free to use any of the following snippets to cherry pick this component.

To import this component from the CDN using a script tag:

<script type="module" src="https://cdn.jsdelivr.net/npm/@kubex/zinc@2.0.16/dist/components/infinite-scroll/infinite-scroll.js"></script>

To import this component from the CDN using a JavaScript import:

import 'https://cdn.jsdelivr.net/npm/@kubex/zinc@2.0.16/dist/components/infinite-scroll/infinite-scroll.js';

To import this component using a bundler:

import '@kubex/zinc/dist/components/infinite-scroll/infinite-scroll.js';

Slots

Name Description
(default) The first page of content. Later pages are appended here.
loader Placeholder content shown at the end while a page loads. Defaults to a skeleton bar.

Learn more about using slots.

Properties

Name Description Reflects Type Default
uri The page URL, with #page# replaced by the page number to load. string ''
page The last page already rendered. The next request is for page + 1. When it isn’t set and the component starts empty, the first request is for page 1. number 1
finished Set once there is nothing more to load. boolean false
updateComplete A read-only promise that resolves when the component has finished updating.

Learn more about attributes and properties.

Events

Name React Event Description Event Detail
zn-load Emitted after a page has been appended. -
zn-finish Emitted when a page comes back empty, so there is nothing more to load. -
zn-error Emitted when a page request fails. Loading stops. -

Learn more about events.

Parts

Name Description
loader The default skeleton bar shown while a page loads.

Learn more about customizing CSS parts.

Dependencies

This component automatically imports the following dependencies.

  • <zn-skeleton>