Pagination
Page navigation with composable page links, previous/next buttons, and ellipsis indicators.
Import
import {
AvPaginationImports,
getPaginationRange,
} from '@avesra/angular';Usage
import { Component, computed, model } from '@angular/core';
import { AvPaginationImports } from '@avesra/angular';
@Component({
selector: 'app-pagination-basic-demo',
imports: [AvPaginationImports],
template: `<av-pagination class="justify-center">
<ul av-pagination-content>
<li av-pagination-item>
<button
av-pagination-prev
[disabled]="page() === 1"
(click)="page.set(page() - 1)"
>
<span av-pagination-prev-icon></span>
<span>Previous</span>
</button>
</li>
@for (p of pages(); track p) {
<li av-pagination-item>
<button
av-pagination-link
[active]="p === page()"
(click)="page.set(p)"
>
{{ p }}
</button>
</li>
}
<li av-pagination-item>
<button
av-pagination-next
[disabled]="page() === totalPages"
(click)="page.set(page() + 1)"
>
<span>Next</span>
<span av-pagination-next-icon></span>
</button>
</li>
</ul>
</av-pagination>`,
})
export class PaginationBasicDemo {
readonly page = model(1);
readonly totalPages = 3;
readonly pages = computed(() =>
Array.from({ length: this.totalPages }, (_, index) => index + 1),
);
}Anatomy
Import the Pagination parts and compose them with element and attribute selectors. Optional pieces include div[av-pagination-summary] and span[av-pagination-ellipsis].
<av-pagination>
<div av-pagination-summary>Showing 1-10 of 100 results</div>
<ul av-pagination-content>
<li av-pagination-item>
<button av-pagination-prev>
<span av-pagination-prev-icon></span>
<span>Previous</span>
</button>
</li>
<li av-pagination-item>
<button av-pagination-link [active]="true">1</button>
</li>
<li av-pagination-item>
<span av-pagination-ellipsis></span>
</li>
<li av-pagination-item>
<button av-pagination-link>10</button>
</li>
<li av-pagination-item>
<button av-pagination-next>
<span>Next</span>
<span av-pagination-next-icon></span>
</button>
</li>
</ul>
</av-pagination>Sizes
Set size on av-pagination — sm, md (default), or lg.
import { Component, computed, model } from '@angular/core';
import { AvPaginationImports } from '@avesra/angular';
@Component({
selector: 'app-pagination-sizes-demo',
imports: [AvPaginationImports],
template: `<div class="flex flex-col gap-6">
<div class="flex flex-col gap-2">
<span class="text-xs font-medium text-muted capitalize">sm</span>
<av-pagination class="justify-center" size="sm">
<ul av-pagination-content>
<li av-pagination-item>
<button av-pagination-prev [disabled]="smPage() === 1" (click)="smPage.set(smPage() - 1)">
<span av-pagination-prev-icon></span>
<span>Previous</span>
</button>
</li>
@for (p of pages(); track p) {
<li av-pagination-item>
<button av-pagination-link [active]="p === smPage()" (click)="smPage.set(p)">{{ p }}</button>
</li>
}
<li av-pagination-item>
<button av-pagination-next [disabled]="smPage() === totalPages" (click)="smPage.set(smPage() + 1)">
<span>Next</span>
<span av-pagination-next-icon></span>
</button>
</li>
</ul>
</av-pagination>
</div>
<div class="flex flex-col gap-2">
<span class="text-xs font-medium text-muted capitalize">md</span>
<av-pagination class="justify-center">
<ul av-pagination-content>
<li av-pagination-item>
<button av-pagination-prev [disabled]="mdPage() === 1" (click)="mdPage.set(mdPage() - 1)">
<span av-pagination-prev-icon></span>
<span>Previous</span>
</button>
</li>
@for (p of pages(); track p) {
<li av-pagination-item>
<button av-pagination-link [active]="p === mdPage()" (click)="mdPage.set(p)">{{ p }}</button>
</li>
}
<li av-pagination-item>
<button av-pagination-next [disabled]="mdPage() === totalPages" (click)="mdPage.set(mdPage() + 1)">
<span>Next</span>
<span av-pagination-next-icon></span>
</button>
</li>
</ul>
</av-pagination>
</div>
<div class="flex flex-col gap-2">
<span class="text-xs font-medium text-muted capitalize">lg</span>
<av-pagination class="justify-center" size="lg">
<ul av-pagination-content>
<li av-pagination-item>
<button av-pagination-prev [disabled]="lgPage() === 1" (click)="lgPage.set(lgPage() - 1)">
<span av-pagination-prev-icon></span>
<span>Previous</span>
</button>
</li>
@for (p of pages(); track p) {
<li av-pagination-item>
<button av-pagination-link [active]="p === lgPage()" (click)="lgPage.set(p)">{{ p }}</button>
</li>
}
<li av-pagination-item>
<button av-pagination-next [disabled]="lgPage() === totalPages" (click)="lgPage.set(lgPage() + 1)">
<span>Next</span>
<span av-pagination-next-icon></span>
</button>
</li>
</ul>
</av-pagination>
</div>
</div>`,
})
export class PaginationSizesDemo {
readonly smPage = model(1);
readonly mdPage = model(1);
readonly lgPage = model(1);
readonly totalPages = 3;
readonly pages = computed(() =>
Array.from({ length: this.totalPages }, (_, index) => index + 1),
);
}Disabled
Set disabled on button[av-pagination-prev], button[av-pagination-next], or button[av-pagination-link] to prevent interaction.
import { Component, computed, model } from '@angular/core';
import { AvPaginationImports } from '@avesra/angular';
@Component({
selector: 'app-pagination-disabled-demo',
imports: [AvPaginationImports],
template: `<av-pagination class="justify-center">
<ul av-pagination-content>
<li av-pagination-item>
<button av-pagination-prev disabled>
<span av-pagination-prev-icon></span>
<span>Previous</span>
</button>
</li>
@for (p of pages(); track p) {
<li av-pagination-item>
<button
av-pagination-link
[active]="p === page()"
(click)="page.set(p)"
>
{{ p }}
</button>
</li>
}
<li av-pagination-item>
<button av-pagination-next disabled>
<span>Next</span>
<span av-pagination-next-icon></span>
</button>
</li>
</ul>
</av-pagination>`,
})
export class PaginationDisabledDemo {
readonly page = model(1);
readonly totalPages = 3;
readonly pages = computed(() =>
Array.from({ length: this.totalPages }, (_, index) => index + 1),
);
}Simple (Previous / Next)
A minimal pattern with only previous and next controls plus a summary line.
import { Component, computed, model } from '@angular/core';
import { AvPaginationImports } from '@avesra/angular';
@Component({
selector: 'app-pagination-simple-prev-next-demo',
imports: [AvPaginationImports],
template: `<av-pagination class="w-full">
<div av-pagination-summary>
{{ startItem() }} to {{ endItem() }} of {{ totalItems }} invoices
</div>
<ul av-pagination-content>
<li av-pagination-item>
<button
av-pagination-prev
[disabled]="page() === 1"
(click)="page.set(page() - 1)"
>
<span av-pagination-prev-icon></span>
<span>Prev</span>
</button>
</li>
<li av-pagination-item>
<button
av-pagination-next
[disabled]="page() === totalPages"
(click)="page.set(page() + 1)"
>
<span>Next</span>
<span av-pagination-next-icon></span>
</button>
</li>
</ul>
</av-pagination>`,
})
export class PaginationSimplePrevNextDemo {
readonly page = model(1);
readonly totalPages = 10;
readonly itemsPerPage = 5;
readonly totalItems = 50;
readonly startItem = computed(() => (this.page() - 1) * this.itemsPerPage + 1);
readonly endItem = computed(() =>
Math.min(this.page() * this.itemsPerPage, this.totalItems),
);
}Controlled
Drive the current page with a model() signal and update it from (click) handlers on page links and prev/next buttons.
import { Component, computed, model } from '@angular/core';
import {
getPaginationRange,
AvPaginationImports,
} from '@avesra/angular';
@Component({
selector: 'app-pagination-controlled-demo',
imports: [AvPaginationImports],
template: `<av-pagination>
<div av-pagination-summary>
Showing {{ startItem() }}-{{ endItem() }} of {{ totalItems }} results
</div>
<ul av-pagination-content>
<li av-pagination-item>
<button
av-pagination-prev
[disabled]="page() === 1"
(click)="page.set(page() - 1)"
>
<span av-pagination-prev-icon></span>
<span>Previous</span>
</button>
</li>
@for (item of range(); track $index) {
<li av-pagination-item>
@if (item === 'ellipsis') {
<span av-pagination-ellipsis></span>
} @else {
<button
av-pagination-link
[active]="item === page()"
(click)="page.set(item)"
>
{{ item }}
</button>
}
</li>
}
<li av-pagination-item>
<button
av-pagination-next
[disabled]="page() === totalPages"
(click)="page.set(page() + 1)"
>
<span>Next</span>
<span av-pagination-next-icon></span>
</button>
</li>
</ul>
</av-pagination>`,
})
export class PaginationControlledDemo {
readonly page = model(1);
readonly totalPages = 12;
readonly itemsPerPage = 10;
readonly totalItems = 120;
readonly range = computed(() => getPaginationRange(this.page(), this.totalPages));
readonly startItem = computed(() => (this.page() - 1) * this.itemsPerPage + 1);
readonly endItem = computed(() =>
Math.min(this.page() * this.itemsPerPage, this.totalItems),
);
}With Ellipsis
Use getPaginationRange() from @avesra/angular to compute visible page numbers and insert span[av-pagination-ellipsis] between ranges.
import { Component, computed, model } from '@angular/core';
import {
getPaginationRange,
AvPaginationImports,
} from '@avesra/angular';
@Component({
selector: 'app-pagination-with-ellipsis-demo',
imports: [AvPaginationImports],
template: `<div class="w-full max-w-2xs overflow-x-auto sm:max-w-full">
<av-pagination class="justify-center">
<ul av-pagination-content>
<li av-pagination-item>
<button
av-pagination-prev
[disabled]="page() === 1"
(click)="page.set(page() - 1)"
>
<span av-pagination-prev-icon></span>
<span>Previous</span>
</button>
</li>
@for (item of range(); track $index) {
<li av-pagination-item>
@if (item === 'ellipsis') {
<span av-pagination-ellipsis></span>
} @else {
<button
av-pagination-link
[active]="item === page()"
(click)="page.set(item)"
>
{{ item }}
</button>
}
</li>
}
<li av-pagination-item>
<button
av-pagination-next
[disabled]="page() === totalPages"
(click)="page.set(page() + 1)"
>
<span>Next</span>
<span av-pagination-next-icon></span>
</button>
</li>
</ul>
</av-pagination>
</div>`,
})
export class PaginationWithEllipsisDemo {
readonly page = model(1);
readonly totalPages = 12;
readonly range = computed(() => getPaginationRange(this.page(), this.totalPages));
}With Summary
Add div[av-pagination-summary] alongside the content list to show result counts.
import { Component, computed, model } from '@angular/core';
import {
getPaginationRange,
AvPaginationImports,
} from '@avesra/angular';
@Component({
selector: 'app-pagination-with-summary-demo',
imports: [AvPaginationImports],
template: `<av-pagination class="w-full">
<div av-pagination-summary>
Showing {{ startItem() }}-{{ endItem() }} of {{ totalItems }} results
</div>
<ul av-pagination-content>
<li av-pagination-item>
<button
av-pagination-prev
[disabled]="page() === 1"
(click)="page.set(page() - 1)"
>
<span av-pagination-prev-icon></span>
<span>Previous</span>
</button>
</li>
@for (item of range(); track $index) {
<li av-pagination-item>
@if (item === 'ellipsis') {
<span av-pagination-ellipsis></span>
} @else {
<button
av-pagination-link
[active]="item === page()"
(click)="page.set(item)"
>
{{ item }}
</button>
}
</li>
}
<li av-pagination-item>
<button
av-pagination-next
[disabled]="page() === totalPages"
(click)="page.set(page() + 1)"
>
<span>Next</span>
<span av-pagination-next-icon></span>
</button>
</li>
</ul>
</av-pagination>`,
})
export class PaginationWithSummaryDemo {
readonly page = model(1);
readonly totalPages = 12;
readonly itemsPerPage = 10;
readonly totalItems = 120;
readonly range = computed(() => getPaginationRange(this.page(), this.totalPages));
readonly startItem = computed(() => (this.page() - 1) * this.itemsPerPage + 1);
readonly endItem = computed(() =>
Math.min(this.page() * this.itemsPerPage, this.totalItems),
);
}Custom Icons
Replace the default chevrons by projecting custom content into span[av-pagination-prev-icon] and span[av-pagination-next-icon].
import { Component, computed, model } from '@angular/core';
import { AvPaginationImports } from '@avesra/angular';
@Component({
selector: 'app-pagination-custom-icons-demo',
imports: [AvPaginationImports],
template: `<av-pagination class="justify-center">
<ul av-pagination-content>
<li av-pagination-item>
<button
av-pagination-prev
[disabled]="page() === 1"
(click)="page.set(page() - 1)"
>
<span av-pagination-prev-icon>
<svg
class="size-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<line x1="19" y1="12" x2="5" y2="12" />
<polyline points="12 19 5 12 12 5" />
</svg>
</span>
<span>Back</span>
</button>
</li>
@for (p of pages(); track p) {
<li av-pagination-item>
<button
av-pagination-link
[active]="p === page()"
(click)="page.set(p)"
>
{{ p }}
</button>
</li>
}
<li av-pagination-item>
<button
av-pagination-next
[disabled]="page() === totalPages"
(click)="page.set(page() + 1)"
>
<span>Forward</span>
<span av-pagination-next-icon>
<svg
class="size-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" />
</svg>
</span>
</button>
</li>
</ul>
</av-pagination>`,
})
export class PaginationCustomIconsDemo {
readonly page = model(1);
readonly totalPages = 3;
readonly pages = computed(() =>
Array.from({ length: this.totalPages }, (_, index) => index + 1),
);
}Styling
Passing Tailwind CSS classes
Pass utility classes on individual Pagination parts — for example av-pagination, ul[av-pagination-content], and button[av-pagination-link].
import { Component, computed, model } from '@angular/core';
import { AvPaginationImports } from '@avesra/angular';
@Component({
selector: 'app-pagination-custom-styling-demo',
imports: [AvPaginationImports],
template: `<av-pagination class="gap-8 justify-center">
<ul av-pagination-content class="gap-2">
<li av-pagination-item>
<button
av-pagination-prev
[disabled]="page() === 1"
(click)="page.set(page() - 1)"
>
<span av-pagination-prev-icon></span>
<span>Previous</span>
</button>
</li>
@for (p of pages(); track p) {
<li av-pagination-item>
<button
av-pagination-link
class="rounded-md"
[active]="p === page()"
(click)="page.set(p)"
>
{{ p }}
</button>
</li>
}
<li av-pagination-item>
<button
av-pagination-next
[disabled]="page() === totalPages"
(click)="page.set(page() + 1)"
>
<span>Next</span>
<span av-pagination-next-icon></span>
</button>
</li>
</ul>
</av-pagination>`,
})
export class PaginationCustomStylingDemo {
readonly page = model(1);
readonly totalPages = 3;
readonly pages = computed(() =>
Array.from({ length: this.totalPages }, (_, index) => index + 1),
);
}Customizing the component classes
To customize the Pagination classes, use the @layer components directive. Learn more.
@layer components {
.av-pagination {
@apply gap-8;
}
.av-pagination__link {
@apply rounded-md;
}
.av-pagination__summary {
@apply text-xs font-semibold;
}
}Avesra follows the BEM methodology so component variants and states stay reusable and easy to customize.
CSS Classes
The Pagination component uses these CSS classes:
Base & Layout Classes
.av-pagination— Root navigation container with flex layout.av-pagination__summary— Left-side info text container.av-pagination__content— Container for pagination items.av-pagination__item— Individual item wrapper.av-pagination__link— Page number button.av-pagination__link--nav— Navigation button modifier (Previous/Next).av-pagination__ellipsis— Ellipsis indicator
Size Classes
.av-pagination--sm— Small size variant.av-pagination--md— Medium size variant (default).av-pagination--lg— Large size variant
Interactive States
The component supports both CSS pseudo-classes and data attributes:
- Active page:
[data-active="true"]or[aria-current="page"] - Hover:
:hoveror[data-hovered="true"] - Focus:
:focus-visibleor[data-focus-visible="true"] - Disabled:
:disabledor[aria-disabled="true"] - Pressed:
:activeor[data-pressed="true"]
API Reference
Compound API across av-pagination, summary, content, item, link, prev/next, and ellipsis parts. Page changes use native (click) handlers. Use getPaginationRange() to build ellipsis-aware page lists.
| Prop | Type | Default | Description |
|---|---|---|---|
size | 'sm' | 'md' | 'lg' | 'md' | Size of the pagination items (av-pagination). |
active | boolean | false | Whether this is the current page (button[av-pagination-link]). |
disabled | boolean | false | Whether the control is disabled (button[av-pagination-link], button[av-pagination-prev], button[av-pagination-next]). |
Accessibility
Pagination provides:
- Semantic
<nav>viarole="navigation"witharia-label="pagination"onav-pagination - Active page indicated via
aria-current="page"onbutton[av-pagination-link] - Keyboard navigation via Tab through all interactive elements
- Focus ring on keyboard navigation via
:focus-visible - Ellipsis marked with
aria-hidden="true"to avoid screen reader confusion - Disabled states communicated via native
disabledandaria-disabled