Paginating a long list

This recipe shows how to split a long list of items across numbered pages using the p:paginate attribute. Pagination runs during server-side composition — no JavaScript is required.

When to use this approach

Use pagination when a composed element produces more children than should appear in a single response. A contact directory with hundreds of entries, a blog archive with years of posts, a product catalogue — any list that would be unwieldy as a single page.

Add the attribute

Place p:paginate on the container element whose direct children should be split across pages. The attribute value is the number of items per page:

<ul id="contacts" xmlns:p="https://pagelove.org/1.0" p:paginate="10">
  <li itemscope itemtype="https://example.com/Contact">
    <span itemprop="name">Alice Nguyen</span>
    <span itemprop="email">alice@example.com</span>
  </li>
  <li itemscope itemtype="https://example.com/Contact">
    <span itemprop="name">Bob Rivera</span>
    <span itemprop="email">bob@example.com</span>
  </li>
  <!-- ... more contacts ... -->
</ul>

The xmlns:p namespace declaration is required on the element (or an ancestor) so the attribute is recognized. If the document already declares the namespace higher up, you do not need to repeat it.

How pages are requested

Clients navigate pages with query parameters. Pages are 1-indexed:

/contacts.html                        → page 1 (default)
/contacts.html?paginate:page=2        → page 2
/contacts.html?paginate:page=3&paginate:length=20  → page 3, 20 per page

The paginate:length parameter overrides the default page size set in the attribute. If a requested page number exceeds the total number of pages, it is clamped to the last page.

Pagination automatically generates <link> elements in <head> and HTTP Link headers (RFC 8288) for navigation. A response for page 2 of a 5-page list includes:

Link: </contacts.html?paginate:page=1>; rel="first"
Link: </contacts.html?paginate:page=1>; rel="prev"
Link: </contacts.html?paginate:page=3>; rel="next"
Link: </contacts.html?paginate:page=5>; rel="last"

Corresponding <link> elements appear in the document's <head>. Only the links that make sense are generated — page 1 has no prev, the last page has no next, and a single-page list produces no navigation links at all.

Non-pagination query parameters are preserved in the generated URLs. If the request included ?q=smith&paginate:page=2, the navigation links carry q=smith forward.

Fetching a page fragment with Range

For AJAX-style navigation — replacing the list without reloading the surrounding page — combine pagination with a Range selector request:

GET /contacts.html?paginate:page=2 HTTP/2
Range: selector=ul#contacts

The server composes the full document (including pagination), then extracts the ul#contacts fragment. The response contains only the paginated list for page 2, ready to swap into the DOM.

Multiple paginators on one page

A document can contain more than one paginated element. Each must have a unique id:

<ul id="users" xmlns:p="https://pagelove.org/1.0" p:paginate="5">
  <!-- user items -->
</ul>

<ul id="posts" xmlns:p="https://pagelove.org/1.0" p:paginate="10">
  <!-- post items -->
</ul>

Navigate them independently by prefixing the element's id:

/dashboard.html?paginate:users:page=2&paginate:posts:page=3

Each paginator's generated links preserve the other paginator's current page state.

See also