Pagination
Page navigation links with previous, next and ellipsis.
import { … } from '@inlaylab/ivorie/pagination'import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IV_PAGINATION_IMPORTS } from '@inlaylab/ivorie/pagination';
@Component({
selector: 'demo-pagination',
imports: [IV_PAGINATION_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<nav ivPagination>
<ul ivPaginationContent>
<li ivPaginationItem><a ivPaginationPrevious href="#" (click)="go(page() - 1, $event)"></a></li>
@for (p of pages; track p) {
<li ivPaginationItem>
<a ivPaginationLink href="#" [isActive]="p === page()" (click)="go(p, $event)">{{ p }}</a>
</li>
}
<li ivPaginationItem><a ivPaginationNext href="#" (click)="go(page() + 1, $event)"></a></li>
</ul>
</nav>
`,
})
export default class PaginationDemo {
protected readonly pages = [1, 2, 3, 4, 5];
protected readonly page = signal(2);
protected go(next: number, event: Event) {
event.preventDefault();
this.page.set(Math.min(Math.max(next, 1), this.pages.length));
}
}
Installation
npx ivorie add paginationimport { … } from '@inlaylab/ivorie/pagination'Ports the shadcn/ui Pagination. It is presentational only. You own the page state, the range and the navigation.
Usage
import { IV_PAGINATION_IMPORTS } from '@inlaylab/ivorie/pagination';<nav ivPagination>
<ul ivPaginationContent>
<li ivPaginationItem><a ivPaginationPrevious href="#" (click)="prev()"></a></li>
@for (page of pages; track page) {
<li ivPaginationItem>
<a ivPaginationLink href="#" [isActive]="page === currentPage" (click)="goTo(page)">{{ page }}</a>
</li>
}
<li ivPaginationItem><a ivPaginationNext href="#" (click)="next()"></a></li>
</ul>
</nav>Examples
Ellipsis
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
import { IV_PAGINATION_IMPORTS } from '@inlaylab/ivorie/pagination';
@Component({
selector: 'demo-pagination-ellipsis',
imports: [IV_PAGINATION_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<nav ivPagination>
<ul ivPaginationContent>
<li ivPaginationItem><a ivPaginationPrevious href="#" (click)="go(page() - 1, $event)"></a></li>
<li ivPaginationItem>
<a ivPaginationLink href="#" [isActive]="page() === 1" (click)="go(1, $event)">1</a>
</li>
@if (page() > 3) {
<li ivPaginationItem><span ivPaginationEllipsis></span></li>
}
@for (p of visible(); track p) {
<li ivPaginationItem>
<a ivPaginationLink href="#" [isActive]="p === page()" (click)="go(p, $event)">{{ p }}</a>
</li>
}
@if (page() < total - 2) {
<li ivPaginationItem><span ivPaginationEllipsis></span></li>
}
<li ivPaginationItem>
<a ivPaginationLink href="#" [isActive]="page() === total" (click)="go(total, $event)">{{ total }}</a>
</li>
<li ivPaginationItem><a ivPaginationNext href="#" (click)="go(page() + 1, $event)"></a></li>
</ul>
</nav>
`,
})
export default class PaginationEllipsisDemo {
protected readonly total = 20;
protected readonly page = signal(10);
// The current page and its neighbors, without the first and last page.
protected readonly visible = computed(() =>
[this.page() - 1, this.page(), this.page() + 1].filter((p) => p > 1 && p < this.total),
);
protected go(next: number, event: Event) {
event.preventDefault();
this.page.set(Math.min(Math.max(next, 1), this.total));
}
}
Router links
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { IV_PAGINATION_IMPORTS } from '@inlaylab/ivorie/pagination';
@Component({
selector: 'demo-pagination-router-links',
imports: [RouterLink, IV_PAGINATION_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<nav ivPagination>
<ul ivPaginationContent>
@for (p of pages; track p) {
<li ivPaginationItem>
<a ivPaginationLink routerLink="/docs/pagination" [queryParams]="{ page: p }" [isActive]="p === 1">
{{ p }}
</a>
</li>
}
</ul>
</nav>
`,
})
export default class PaginationRouterLinksDemo {
protected readonly pages = [1, 2, 3];
}
Custom labels
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IV_PAGINATION_IMPORTS } from '@inlaylab/ivorie/pagination';
@Component({
selector: 'demo-pagination-custom-labels',
imports: [IV_PAGINATION_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<nav ivPagination>
<ul ivPaginationContent>
<li ivPaginationItem><a ivPaginationPrevious href="#" text="Prev" (click)="$event.preventDefault()"></a></li>
<li ivPaginationItem>
<a ivPaginationLink href="#" [isActive]="true" (click)="$event.preventDefault()">1</a>
</li>
<li ivPaginationItem><a ivPaginationNext href="#" text="More" (click)="$event.preventDefault()"></a></li>
</ul>
</nav>
`,
})
export default class PaginationCustomLabelsDemo {}
API
Every part also accepts class.
nav[ivPagination]
Root <nav>. No own inputs.
ul[ivPaginationContent]
Flex list. No own inputs.
li[ivPaginationItem]
One entry. No inputs. It has no class input.
a[ivPaginationLink]
| Name | Type | Default | Description |
|---|---|---|---|
isActive |
boolean |
false |
Marks the current page. Uses the outline button look. Others use ghost. |
size |
ButtonVariants['size'] |
'icon' |
Any button size. |
a[ivPaginationPrevious]
| Name | Type | Default | Description |
|---|---|---|---|
text |
string |
'Previous' |
Label. Hidden below the sm breakpoint. |
a[ivPaginationNext]
| Name | Type | Default | Description |
|---|---|---|---|
text |
string |
'Next' |
Label. Hidden below the sm breakpoint. |
span[ivPaginationEllipsis]
Gap marker. No own inputs.
Accessibility
- The root has
role="navigation"andaria-label="pagination". - The active link has
aria-current="page"anddata-active. - Previous and Next have
aria-label"Go to previous page" and "Go to next page". - The ellipsis is
aria-hiddenand holds a screen-reader-only "More pages" text. - Parts are native
nav,ul,lianda. Give links anhreforrouterLinkso they are focusable and keyboard-operable.
Notes
- Selectors sit on native elements. No wrapper divs.
- Disabled state is yours. At the first or last page, add
pointer-events-none opacity-50and setaria-disabledon the link. - Previous and Next chevrons flip in RTL.