pagination

Pagination with page navigation, next and previous links.

PreviousNext
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { ZardPaginationImports } from '../pagination.imports';

@Component({
  selector: 'z-demo-pagination-preview',
  imports: [ZardPaginationImports],
  template: `
    <z-pagination [zTotal]="totalPages" [(zPageIndex)]="currentPage" [zContent]="content" />

    <ng-template #content>
      <ul z-pagination-content>
        <li z-pagination-item>
          <z-pagination-previous (click)="goToPrevious()" [zDisabled]="currentPage() === 1" />
        </li>

        @for (page of pages(); track page) {
          <li z-pagination-item>
            <button type="button" z-pagination-button [zActive]="page === currentPage()" (click)="goToPage(page)">
              <span class="sr-only">To page</span>
              {{ page }}
            </button>
          </li>
        }

        <li z-pagination-item>
          <z-pagination-ellipsis />
        </li>

        <li z-pagination-item>
          <z-pagination-next (click)="goToNext()" [zDisabled]="currentPage() === totalPages" />
        </li>
      </ul>
    </ng-template>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoPaginationPreviewComponent {
  readonly totalPages = 3;
  readonly currentPage = signal(2);

  readonly pages = signal<number[]>(Array.from({ length: this.totalPages }, (_, i) => i + 1));

  goToPage(page: number) {
    this.currentPage.set(page);
  }

  goToPrevious() {
    if (this.currentPage() > 1) {
      this.currentPage.update(p => p - 1);
    }
  }

  goToNext() {
    if (this.currentPage() < this.totalPages) {
      this.currentPage.update(p => p + 1);
    }
  }
}

Installation

Copy
npx zard-cli@latest add pagination

Usage

import { ZardPaginationImports } from '@/shared/components/pagination/pagination.imports';
Copy
<z-pagination [zTotal]="100" [zPageSize]="10"></z-pagination>
Copy

Examples

simple

A simple pagination with only page numbers.
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardPaginationImports } from '../pagination.imports';

@Component({
  selector: 'z-demo-pagination-simple',
  imports: [ZardPaginationImports],
  template: `
    <z-pagination [zTotal]="5" [(zPageIndex)]="currentPage" zSimple />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoPaginationSimpleComponent {
  protected currentPage = 2;
}

icons only

Use just the previous and next buttons without page numbers. This is useful for data tables with a rows per page selector.
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';

import { ZardFieldImports } from '@/shared/components/field';
import { ZardPaginationImports } from '@/shared/components/pagination';
import { ZardSelectImports } from '@/shared/components/select';

@Component({
  selector: 'z-demo-pagination-icons-only',
  imports: [ZardPaginationImports, ZardFieldImports, ZardSelectImports],
  template: `
    <div class="flex w-full justify-around">
      <div class="flex w-fit gap-4">
        <div z-field class="flex-row items-center">
          <label z-field-label for="select-rows-per-page" class="min-w-max">Rows Per Page</label>
          <z-select id="select-rows-per-page" zSize="sm" [(zValue)]="perPage" class="min-w-20">
            <z-select-item zValue="10">10</z-select-item>
            <z-select-item zValue="25">25</z-select-item>
            <z-select-item zValue="50">50</z-select-item>
            <z-select-item zValue="100">100</z-select-item>
          </z-select>
        </div>
        <z-pagination [zTotal]="totalPages()" [(zPageIndex)]="currentPage" [zContent]="content" />
      </div>
    </div>

    <ng-template #content>
      <ul z-pagination-content>
        <li z-pagination-item>
          <z-pagination-previous (click)="goToPrevious()" [zDisabled]="currentPage() === 1" />
        </li>

        <li z-pagination-item>
          <z-pagination-next (click)="goToNext()" [zDisabled]="currentPage() === totalPages()" />
        </li>
      </ul>
    </ng-template>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoPaginationIconsOnlyComponent {
  private totalItems = 350;

  readonly currentPage = signal(1);
  readonly perPage = signal('25');
  readonly totalPages = computed(() => Math.ceil(this.totalItems / parseInt(this.perPage())));

  goToPrevious() {
    if (this.currentPage() > 1) {
      this.currentPage.update(p => p - 1);
    }
  }

  goToNext() {
    if (this.currentPage() < this.totalPages()) {
      this.currentPage.update(p => p + 1);
    }
  }
}

routing

Wire a[z-pagination-button] to Angular RouterLink with [queryParams] and queryParamsHandling="merge" so page links drive real router navigation — the URL updates and the page is bookmarkable, with no full reload.
import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { ActivatedRoute, RouterLink } from '@angular/router';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideChevronLeft, lucideChevronRight } from '@ng-icons/lucide';

import { paginationNextVariants, paginationPreviousVariants } from '@/shared/components/pagination/pagination.variants';

import { ZardPaginationImports } from '../pagination.imports';

@Component({
  selector: 'z-demo-pagination-routing',
  imports: [ZardPaginationImports, RouterLink, NgIcon],
  template: `
    <z-pagination
      zAriaLabel="Routing pagination"
      [zTotal]="totalPages"
      [zPageIndex]="currentPage()"
      [zContent]="content"
    />

    <ng-template #content>
      <ul z-pagination-content>
        <li z-pagination-item>
          <a
            z-pagination-button
            zSize="default"
            [class]="previousClasses"
            [zDisabled]="currentPage() === 1"
            [routerLink]="currentPage() === 1 ? null : []"
            [queryParams]="{ page: previousPage() }"
            queryParamsHandling="merge"
            [attr.aria-disabled]="currentPage() === 1 ? 'true' : null"
          >
            <span class="sr-only">Go to previous page</span>
            <ng-icon name="lucideChevronLeft" aria-hidden="true" />
            <span class="hidden sm:block" aria-hidden="true">Previous</span>
          </a>
        </li>

        @for (page of pages; track page) {
          <li z-pagination-item>
            <a
              z-pagination-button
              [routerLink]="[]"
              [queryParams]="{ page }"
              queryParamsHandling="merge"
              [zActive]="page === currentPage()"
              [attr.aria-current]="page === currentPage() ? 'page' : null"
            >
              <span class="sr-only">To page</span>
              {{ page }}
            </a>
          </li>
        }

        <li z-pagination-item>
          <a
            z-pagination-button
            zSize="default"
            [class]="nextClasses"
            [zDisabled]="currentPage() === totalPages"
            [routerLink]="currentPage() === totalPages ? null : []"
            [queryParams]="{ page: nextPage() }"
            queryParamsHandling="merge"
            [attr.aria-disabled]="currentPage() === totalPages ? 'true' : null"
          >
            <span class="sr-only">Go to next page</span>
            <span class="hidden sm:block" aria-hidden="true">Next</span>
            <ng-icon name="lucideChevronRight" aria-hidden="true" />
          </a>
        </li>
      </ul>
    </ng-template>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  viewProviders: [provideIcons({ lucideChevronLeft, lucideChevronRight })],
})
export class ZardDemoPaginationRoutingComponent {
  private readonly route = inject(ActivatedRoute);

  protected readonly totalPages = 5;
  protected readonly pages = Array.from({ length: this.totalPages }, (_, i) => i + 1);
  protected readonly previousClasses = paginationPreviousVariants();
  protected readonly nextClasses = paginationNextVariants();

  private readonly queryParamMap = toSignal(this.route.queryParamMap, {
    initialValue: this.route.snapshot.queryParamMap,
  });

  protected readonly currentPage = computed(() => {
    const page = Number(this.queryParamMap().get('page'));
    return Number.isInteger(page) && page >= 1 && page <= this.totalPages ? page : 1;
  });

  protected readonly previousPage = computed(() => Math.max(1, this.currentPage() - 1));
  protected readonly nextPage = computed(() => Math.min(this.totalPages, this.currentPage() + 1));
}

API Reference

z-paginationComponent

Renders as a `role="group"` element (not a `<nav>` landmark) with an `[zAriaLabel]`-driven accessible name. Given `[zTotal]` and `[(zPageIndex)]`, it renders and manages the full previous/numbers/next navigation itself and calls `goToPage()` internally on click; pass `[zContent]` to replace that markup with your own composition (e.g. to wire real `routerLink`/`href` page links), in which case `[zTotal]`/`[zPageIndex]`/`[zSimple]`/`[zSize]`/`[zDisabled]` are ignored by the root and it is up to the projected content to use them.

PropertyDescriptionTypeDefault
[class] Custom CSS classes string ''
[zAriaLabel] Accessible name for the pagination element, rendered as its aria-label. string 'Pagination'
[zContent] Custom pagination structure, replacing the built-in previous/numbers/next markup. TemplateRef<void> | undefined undefined
[zDisabled] Disables pagination interaction boolean false
[zPageIndex] Current page, two-way bindable number 1
[(zPageIndex)] Current page index number 1
[zSimple] A simple pagination with only page numbers. boolean false
[zSize] Size of the numbered page buttons. 'icon' | 'icon-xs' | 'icon-sm' | 'icon-lg' 'icon'
[zTotal] Total number of pages number 1

ul[z-pagination-content]Component

Container (unordered list) for pagination content (buttons and ellipsis).

PropertyDescriptionTypeDefault
[class] Custom CSS classes string ''

li[z-pagination-item]Component

Wraps a pagination button or ellipsis as li element of container.

button[z-pagination-button]Component

A single page button, styled via an internal z-button. Also usable as `a[z-pagination-button]` — render it on an anchor and stack Angular's `RouterLink` (this directive does not reimplement router inputs of its own) to make page links drive real navigation, as the `routing` example does.

PropertyDescriptionTypeDefault
[class] Custom CSS classes string ''
[zActive] Whether the button is currently active boolean false
[zDisabled] Whether the button is disabled boolean false
[zSize] Button size 'default' | 'xs' | 'sm' | 'lg' | 'icon' | 'icon-xs' | 'icon-sm' | 'icon-lg' 'icon'

z-pagination-previousComponent

Button to navigate to the previous page. Its "Previous" text and "To previous page" screen-reader label are fixed, not exposed as an input — for a translated or custom label, build the previous button yourself with a `[zContent]` template instead.

PropertyDescriptionTypeDefault
[class] Custom CSS classes string ''
[zDisabled] Whether the button is disabled boolean false
[zSize] Button size 'default' | 'xs' | 'sm' | 'lg' 'default'

z-pagination-nextComponent

Button to navigate to the next page. Its "Next" text and "To next page" screen-reader label are fixed, not exposed as an input — for a translated or custom label, build the next button yourself with a `[zContent]` template instead.

PropertyDescriptionTypeDefault
[class] Custom CSS classes string ''
[zDisabled] Whether the button is disabled boolean false
[zSize] Button size 'default' | 'xs' | 'sm' | 'lg' 'default'

z-pagination-ellipsisComponent

Visual ellipsis ("...") for omitted pages.

PropertyDescriptionTypeDefault
[class] Custom CSS classes string ''
github iconwhatsapp icondiscord iconX icon

Made with ❤ in Brazil. Open source and available on GitHub .