Pagination

Pagination with page navigation, next and previous links.

PreviousNext
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
 
import { ZardPaginationModule } from '../pagination.module';
 
@Component({
  selector: 'z-demo-pagination-default',
  imports: [ZardPaginationModule, FormsModule],
  standalone: true,
  template: ` <z-pagination [zPageIndex]="currentPage" [zTotal]="5" [(ngModel)]="currentPage" /> `,
})
export class ZardDemoPaginationDefaultComponent {
  protected currentPage = 2;
}
 

Installation

1

Run the CLI

Use the CLI to add the component to your project.

npx @ngzard/ui@latest add pagination
1

Add the component files

Create the component directory structure and add the following files to your project.

pagination.component.ts
pagination.component.ts
import {
  booleanAttribute,
  ChangeDetectionStrategy,
  Component,
  computed,
  forwardRef,
  input,
  linkedSignal,
  output,
  ViewEncapsulation,
} from '@angular/core';
import { type ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
 
import type { ClassValue } from 'clsx';
 
import {
  paginationContentVariants,
  paginationEllipsisVariants,
  paginationItemVariants,
  paginationNextVariants,
  paginationPreviousVariants,
  paginationVariants,
} from './pagination.variants';
import { mergeClasses } from '../../shared/utils/utils';
import { ZardButtonComponent } from '../button/button.component';
import { type ZardButtonVariants } from '../button/button.variants';
import { ZardIconComponent } from '../icon/icon.component';
 
@Component({
  selector: 'z-pagination-content',
  template: `
    <div [attr.aria-label]="ariaLabel()" role="navigation" data-slot="pagination-content" [class]="classes()">
      <ng-content />
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  exportAs: 'zPaginationContent',
})
export class ZardPaginationContentComponent {
  readonly ariaLabel = input<string>('pagination-content');
 
  readonly class = input<ClassValue>('');
 
  protected readonly classes = computed(() => mergeClasses(paginationContentVariants(), this.class()));
}
 
@Component({
  selector: 'z-pagination-item',
  template: `
    <div data-slot="pagination-item" [class]="classes()">
      <ng-content />
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  exportAs: 'zPaginationItem',
})
export class ZardPaginationItemComponent {
  readonly class = input<ClassValue>('');
 
  protected readonly classes = computed(() => mergeClasses(paginationItemVariants(), this.class()));
}
 
@Component({
  selector: 'z-pagination-button',
  imports: [ZardButtonComponent],
  standalone: true,
  template: `
    <button
      z-button
      data-slot="pagination-button"
      [attr.aria-disabled]="zDisabled() || null"
      [attr.data-disabled]="zDisabled() || null"
      [attr.aria-current]="zActive() ? 'page' : undefined"
      [attr.data-active]="zActive() || null"
      [zType]="zType()"
      [zSize]="zSize()"
      [class]="class()"
      (click)="handleClick()"
    >
      <ng-content />
    </button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  exportAs: 'zPaginationButton',
})
export class ZardPaginationButtonComponent {
  readonly zDisabled = input(false, { transform: booleanAttribute });
  readonly zActive = input(false, { transform: booleanAttribute });
  readonly zSize = input<ZardButtonVariants['zSize']>();
 
  readonly class = input<ClassValue>('');
  readonly zClick = output<void>();
 
  protected readonly zType = computed<ZardButtonVariants['zType']>(() => (this.zActive() ? 'outline' : 'ghost'));
 
  handleClick() {
    if (!this.zDisabled() && !this.zActive()) {
      this.zClick.emit();
    }
  }
}
 
@Component({
  selector: 'z-pagination-previous',
  imports: [ZardPaginationButtonComponent, ZardIconComponent],
  template: `
    <z-pagination-button aria-label="Go to previous page" [class]="classes()" zSize="default">
      <z-icon zType="chevron-left" />
      <span class="hidden sm:block">Previous</span>
    </z-pagination-button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  exportAs: 'zPaginationPrevious',
})
export class ZardPaginationPreviousComponent {
  readonly class = input<ClassValue>('');
 
  protected readonly classes = computed(() => mergeClasses(paginationPreviousVariants(), this.class()));
}
 
@Component({
  selector: 'z-pagination-next',
  imports: [ZardPaginationButtonComponent, ZardIconComponent],
  template: `
    <z-pagination-button aria-label="Go to next page" [class]="classes()" zSize="default">
      <span class="hidden sm:block">Next</span>
      <z-icon zType="chevron-right" />
    </z-pagination-button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  exportAs: 'zPaginationNext',
})
export class ZardPaginationNextComponent {
  readonly class = input<ClassValue>('');
 
  protected readonly classes = computed(() => mergeClasses(paginationNextVariants(), this.class()));
}
 
@Component({
  selector: 'z-pagination-ellipsis',
  imports: [ZardIconComponent],
  template: `
    <z-icon zType="ellipsis" aria-hidden="true" role="presentation" />
    <span class="sr-only">More pages</span>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  host: {
    '[class]': 'classes()',
  },
  exportAs: 'zPaginationEllipsis',
})
export class ZardPaginationEllipsisComponent {
  readonly class = input<ClassValue>('');
 
  protected readonly classes = computed(() => mergeClasses(paginationEllipsisVariants(), this.class()));
}
 
@Component({
  selector: 'z-pagination',
  imports: [
    ZardPaginationContentComponent,
    ZardPaginationItemComponent,
    ZardPaginationButtonComponent,
    ZardIconComponent,
  ],
  template: `
    <z-pagination-content>
      <z-pagination-item>
        <z-pagination-button
          aria-label="Go to previous page"
          [zSize]="zSize()"
          [zDisabled]="disabled() || currentPage() === 1"
          (zClick)="goToPrevious()"
        >
          <z-icon zType="chevron-left" />
        </z-pagination-button>
      </z-pagination-item>
 
      @for (page of pages(); track page) {
        <z-pagination-item>
          <z-pagination-button
            [zSize]="zSize()"
            [zActive]="page === currentPage()"
            [zDisabled]="disabled()"
            (zClick)="goToPage(page)"
          >
            {{ page }}
          </z-pagination-button>
        </z-pagination-item>
      }
 
      <z-pagination-item>
        <z-pagination-button
          aria-label="Go to next page"
          [zSize]="zSize()"
          [zDisabled]="disabled() || currentPage() === zTotal()"
          (zClick)="goToNext()"
        >
          <z-icon zType="chevron-right" />
        </z-pagination-button>
      </z-pagination-item>
    </z-pagination-content>
  `,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => ZardPaginationComponent),
      multi: true,
    },
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  host: {
    '[class]': 'classes()',
  },
  exportAs: 'zPagination',
})
export class ZardPaginationComponent implements ControlValueAccessor {
  readonly zPageIndex = input<number>(1);
  readonly zTotal = input<number>(1);
  readonly zSize = input<ZardButtonVariants['zSize']>();
  readonly zDisabled = input(false, { transform: booleanAttribute });
 
  readonly class = input<ClassValue>('');
 
  readonly zPageIndexChange = output<number>();
 
  protected readonly classes = computed(() => mergeClasses(paginationVariants(), this.class()));
 
  protected readonly disabled = linkedSignal(() => this.zDisabled());
 
  readonly currentPage = linkedSignal(this.zPageIndex);
 
  readonly pages = computed<number[]>(() => Array.from({ length: Math.max(0, this.zTotal()) }, (_, i) => i + 1));
 
  goToPage(page: number): void {
    if (this.disabled()) return;
    if (page !== this.currentPage() && page >= 1 && page <= this.zTotal()) {
      this.currentPage.set(page);
      this.zPageIndexChange.emit(page);
      this.onChange(page);
      this.onTouched();
    }
  }
 
  goToPrevious() {
    this.goToPage(this.currentPage() - 1);
  }
 
  goToNext() {
    this.goToPage(this.currentPage() + 1);
  }
 
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  private onChange: (value: number) => void = () => {};
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  private onTouched: () => void = () => {};
 
  writeValue(value: number): void {
    this.currentPage.set(value);
  }
 
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
 
  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }
 
  setDisabledState(isDisabled: boolean): void {
    this.disabled.set(isDisabled);
  }
}
 
pagination.variants.ts
pagination.variants.ts
import { cva, type VariantProps } from 'class-variance-authority';
 
export const paginationContentVariants = cva('flex flex-row items-center gap-1');
export type ZardPaginationContentVariants = VariantProps<typeof paginationContentVariants>;
 
export const paginationItemVariants = cva('');
export type ZardPaginationItemVariants = VariantProps<typeof paginationItemVariants>;
 
export const paginationPreviousVariants = cva('gap-1 px-2.5 sm:pl-2.5');
export type ZardPaginationPreviousVariants = VariantProps<typeof paginationPreviousVariants>;
 
export const paginationNextVariants = cva('gap-1 px-2.5 sm:pr-2.5');
export type ZardPaginationNextVariants = VariantProps<typeof paginationNextVariants>;
 
export const paginationEllipsisVariants = cva('flex size-9 items-center justify-center');
export type ZardPaginationEllipsisVariants = VariantProps<typeof paginationEllipsisVariants>;
 
export const paginationVariants = cva('mx-auto flex w-full justify-center');
export type ZardPaginationVariants = VariantProps<typeof paginationVariants>;
 
pagination.module.ts
pagination.module.ts
import { NgModule } from '@angular/core';
 
import {
  ZardPaginationButtonComponent,
  ZardPaginationComponent,
  ZardPaginationContentComponent,
  ZardPaginationEllipsisComponent,
  ZardPaginationItemComponent,
  ZardPaginationNextComponent,
  ZardPaginationPreviousComponent,
} from './pagination.component';
 
const components = [
  ZardPaginationContentComponent,
  ZardPaginationItemComponent,
  ZardPaginationButtonComponent,
  ZardPaginationPreviousComponent,
  ZardPaginationNextComponent,
  ZardPaginationEllipsisComponent,
  ZardPaginationComponent,
];
 
@NgModule({
  imports: components,
  exports: components,
})
export class ZardPaginationModule {}
 

Examples

default

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
 
import { ZardPaginationModule } from '../pagination.module';
 
@Component({
  selector: 'z-demo-pagination-default',
  imports: [ZardPaginationModule, FormsModule],
  standalone: true,
  template: ` <z-pagination [zPageIndex]="currentPage" [zTotal]="5" [(ngModel)]="currentPage" /> `,
})
export class ZardDemoPaginationDefaultComponent {
  protected currentPage = 2;
}
 

custom

import { Component, signal } from '@angular/core';
 
import { ZardPaginationModule } from '../pagination.module';
 
@Component({
  selector: 'z-demo-pagination-custom',
  imports: [ZardPaginationModule],
  standalone: true,
  template: `
    <z-pagination-content ariaLabel="Page navigation">
      <z-pagination-item>
        <z-pagination-previous (click)="goToPrevious()" />
      </z-pagination-item>
 
      @for (page of pages(); track page) {
        <z-pagination-item>
          <z-pagination-button [zActive]="page === currentPage()" (click)="goToPage(page)">
            {{ page }}
          </z-pagination-button>
        </z-pagination-item>
      }
 
      <z-pagination-item>
        <z-pagination-next (click)="goToNext()" />
      </z-pagination-item>
    </z-pagination-content>
  `,
})
export class ZardDemoPaginationCustomComponent {
  readonly totalPages = 5;
  readonly currentPage = signal(3);
 
  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);
    }
  }
}
 

API

[z-pagination] Component

Pagination component with previous, next, and numbered page navigation. Supports two-way binding with [(ngModel)] and form integration via ControlValueAccessor.

Properties

Property Description Type Default
[class] Custom CSS classes string ''
[zSize] Button size 'sm' | 'md' | 'lg' | 'icon' 'icon'
[zPageIndex] Current page index (1-based) number 1
[zTotal] Total number of pages number 1
[zDisabled] Disables pagination interaction boolean false

Events

Event Description Payload
(zPageIndexChange) Emitted when the current page changes number

Forms

This component implements ControlValueAccessor, so it can be used with [(ngModel)] and Reactive Forms.


[z-pagination-content] Component

Container for pagination content (buttons and ellipsis).

Properties

Property Description Type Default
[class] Custom CSS classes string ''

[z-pagination-item] Component

Wraps a pagination button or ellipsis.

Properties

Property Description Type Default
[class] Custom CSS classes string ''

[z-pagination-button] Component

Pagination button with support for active and disabled states.

Properties

Property Description Type Default
[zDisabled] Whether the button is disabled boolean false
[zActive] Whether the button is currently active boolean false
[zSize] Button size 'sm' | 'md' | 'lg' | 'icon' 'icon'
[class] Custom CSS classes string ''

Events

Event Description
(zClick) Emitted when clicked (if not disabled or active)

[z-pagination-previous] Component

Button to go to the previous page.

Properties

Property Description Type Default
[class] Custom CSS classes string ''

[z-pagination-next] Component

Button to go to the next page.

Properties

Property Description Type Default
[class] Custom CSS classes string ''

[z-pagination-ellipsis] Component

Visual ellipsis ("…") for omitted pages.

Properties

Property Description Type Default
[class] Custom CSS classes string ''