Paginate a List

When a dataset is too large to display all at once, pagination lets users move through it in fixed-size pages. XMLUI has two paths depending on which component you're working with.

If you're using a Table, pagination is built in — just set isPaginated and the component handles page state for you:

<Table
  data="/api/endpoint"
  isPaginated
  pageSize="10"
  pageSizeOptions="{[5, 10, 20, 30]}"
  paginationControlsLocation="both"
>
    ...
</Table>

For components like List that don't have pagination built in, wire a DataSource and a Pagination component together through reactive variables. The Pagination component reports page changes; you update the offset variables; DataSource re-fetches automatically with the new query parameters.

<App 
  var.pageSize="{5}" 
  var.currentPage="{0}" 
  var.before="{0}" 
  var.after="{pageSize-1}"
>
  <DataSource 
    id="pagination_ds" 
    url="/api/pagination_items" 
    queryParams="{{ from: before, to: after }}" 
  />
  <Pagination
    itemCount="20"
    pageSize="{pageSize}"
    pageIndex="{currentPage}"
    onPageDidChange="(page, size, total) => {
      currentPage = page;
      before = page * size;
      after = before + size - 1;
    }"
  />
  <List data="{pagination_ds}" />
</App>
Paginate a list
<App 
  var.pageSize="{5}" 
  var.currentPage="{0}" 
  var.before="{0}" 
  var.after="{pageSize-1}"
>
  <DataSource 
    id="pagination_ds" 
    url="/api/pagination_items" 
    queryParams="{{ from: before, to: after }}" 
  />
  <Pagination
    itemCount="20"
    pageSize="{pageSize}"
    pageIndex="{currentPage}"
    onPageDidChange="(page, size, total) => {
      currentPage = page;
      before = page * size;
      after = before + size - 1;
    }"
  />
  <List data="{pagination_ds}" />
</App>

Key points

Table has pagination built in — List needs it wired manually: Set isPaginated on Table and it handles page state internally. For List (or any component without built-in pagination), combine a DataSource with a Pagination component and wire them together through reactive variables.

Pagination tells you the new page; you update the variables: The onPageDidChange handler receives (page, size, total). Assign the new page index to currentPage and recalculate before and after offsets — both reactive variables are read by DataSource's queryParams, so the data refetch happens automatically.

queryParams on DataSource drives the API slice: Pass the offset or cursor values as query parameters using queryParams="{{ from: before, to: after }}". The server-side handler uses these to return only the requested page of data. The API shape (offset/limit, cursor, page number) depends on your backend — adjust the variable names and arithmetic accordingly.

itemCount on Pagination must match the total record count from your API: The component uses this number to calculate how many page buttons to show. If your API returns the total count in the response, read it with resultSelector or a separate DataSource and bind it to itemCount.

pageSize controls both the visual controls and the slice math: Declare it as a reactive variable (var.pageSize="{5}") so that if the user changes the page size through Pagination's size selector, the same value flows into the offset recalculation in onPageDidChange and into the DataSource query automatically.


See also