Pagination
A pager whose window is a fixed handful of slots, so a 200-page set draws what a 20-page one does.
import { Pagination } from "@glint/components/pagination.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component Demo inherits Window { width: 620px; height: 200px; background: Tokens.color-background;
in-out property <int> page: 4;
VerticalLayout { padding: 24px; spacing: 16px; alignment: center;
HorizontalLayout { alignment: center;
Pagination { accessible-label: "Invoices"; total: 12; current <=> root.page; } }
Text { text: "Showing page " + (root.page + 1) + " of 12."; color: Tokens.color-muted-foreground; font-size: Tokens.typography-body-sm-size; horizontal-alignment: center; } }}Usage
import { Pagination } from "@glint/components/pagination.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component AppWindow inherits Window { width: 520px; height: 140px; background: Tokens.color-background;
in-out property <int> page: 0;
VerticalLayout { padding: 24px; alignment: center;
HorizontalLayout { alignment: center;
Pagination { accessible-label: "Search results"; total: 8; current <=> root.page; changed(page) => { debug("load page", page); } } } }}total is how many pages there are, counting from one. current is which one
is showing, counting from zero — the index you would use against an array,
not the number drawn on the button. It is two-way, so a pager can be driven
from outside as well as clicked, and changed fires with the new index once a
step or a number moves it.
The pager draws nothing itself but the controls. Loading the page is yours: the component reports where the reader went and stops there.
Examples
The window
A set too long to list windows itself. The pager keeps the first page, the last
page, the sibling-count pages either side of the current one, and an ellipsis
standing for each run in between — so the number of slots is fixed and a
200-page set draws the same handful of buttons a 20-page one does. Below the
window size the set is drawn whole, because an ellipsis standing for one page
saves nothing.
The ellipsis is not a control: it goes nowhere, so it takes no tab stop and leaves no node in the accessibility tree.
import { Pagination } from "@glint/components/pagination.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component Demo inherits Window { width: 660px; height: 260px; background: Tokens.color-background;
in-out property <int> near-start: 1; in-out property <int> middle: 96; in-out property <int> near-end: 198;
VerticalLayout { padding: 24px; spacing: 16px; alignment: center;
HorizontalLayout { alignment: center; Pagination { accessible-label: "Near the start"; total: 200; current <=> root.near-start; } }
HorizontalLayout { alignment: center; Pagination { accessible-label: "In the middle"; total: 200; current <=> root.middle; } }
HorizontalLayout { alignment: center; Pagination { accessible-label: "Near the end"; total: 200; current <=> root.near-end; } } }}How many siblings
sibling-count is how many pages are drawn either side of the current one, and
it is what the window is measured from: the window is both ends, both ellipses
and the current page with its siblings. Raising it widens the pager everywhere,
not only in the middle.
import { Pagination } from "@glint/components/pagination.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component Demo inherits Window { width: 680px; height: 240px; background: Tokens.color-background;
in-out property <int> tight: 50; in-out property <int> wide: 50;
VerticalLayout { padding: 24px; spacing: 16px; alignment: center;
Text { text: "sibling-count: 0"; color: Tokens.color-muted-foreground; font-size: Tokens.typography-body-sm-size; horizontal-alignment: center; } HorizontalLayout { alignment: center; Pagination { accessible-label: "One page either side"; total: 100; sibling-count: 0; current <=> root.tight; } }
Text { text: "sibling-count: 2"; color: Tokens.color-muted-foreground; font-size: Tokens.typography-body-sm-size; horizontal-alignment: center; } HorizontalLayout { alignment: center; Pagination { accessible-label: "Two pages either side"; total: 100; sibling-count: 2; current <=> root.wide; } } }}Taking a section away
show-numbers and show-steps each remove one half of the pager. Numbers
alone are the simple pager; steps alone are the icons-only pair a data table
wears under its rows. A step with nowhere to go dims, refuses the click and
leaves the tab order, which is Button’s own
disabled doing the work.
import { Pagination } from "@glint/components/pagination.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component Demo inherits Window { width: 620px; height: 260px; background: Tokens.color-background;
in-out property <int> simple: 0; in-out property <int> steps-only: 3;
VerticalLayout { padding: 24px; spacing: 16px; alignment: center;
Text { text: "Numbers alone — no Previous or Next steps."; color: Tokens.color-muted-foreground; font-size: Tokens.typography-body-sm-size; horizontal-alignment: center; } HorizontalLayout { alignment: center; Pagination { accessible-label: "Simple pager"; total: 6; show-steps: false; current <=> root.simple; } }
Text { text: "Steps alone."; color: Tokens.color-muted-foreground; font-size: Tokens.typography-body-sm-size; horizontal-alignment: center; } HorizontalLayout { alignment: center; Pagination { accessible-label: "Compact pager"; total: 6; show-numbers: false; current <=> root.steps-only; } } }}Naming the steps
The two steps are icon-only buttons, so their names are strings the component
ships and a call site overrides — previous-label and next-label, both
@tr(…) by default. The page numbers name themselves from their own position.
import { Pagination } from "@glint/components/pagination.slint";import { Tokens } from "@glint/theme/tokens.slint";
export component Demo inherits Window { width: 620px; height: 180px; background: Tokens.color-background;
in-out property <int> page: 2;
VerticalLayout { padding: 24px; alignment: center;
HorizontalLayout { alignment: center;
Pagination { accessible-label: "Chapters"; total: 7; current <=> root.page; previous-label: "Previous chapter"; next-label: "Next chapter"; } } }}API Reference
Properties
| Property | Type | Default | Description |
|---|---|---|---|
total | in int | 1 | Total number of pages (1-based count). |
current | in-out int | 0 | Two-way; the active page (0-indexed internally). |
previous-label | in string | @tr("Previous page") | Name of the icon-only step to the previous page; override to translate. |
next-label | in string | @tr("Next page") | Name of the icon-only step to the next page; override to translate. |
sibling-count | in int | 1 | Pages drawn either side of the current one once the set is windowed. |
show-numbers | in bool | true | Draw the page numbers. Off leaves the steps alone — the icons-only pager. |
show-steps | in bool | true | Draw the previous / next steps. Off leaves the numbers alone — the simple pager. |
Callbacks
| Callback | Description |
|---|---|
changed(int) | Fired with the new page index on click. |
Accessibility
- A navigation landmark. The pager carries
accessible-role: navigationand publishesaccessible-item-countastotal. Naming it is the call site’s — setaccessible-label, so a page with a pager over its table and another under it tells the two apart. - Every destination is a named button. A page number announces itself
Page 4, carries its index and reports whether it is the current page (accessible-item-selected), so a screen reader says where the reader is without the visual highlight. - The steps carry their own names and their enabled state. Previous and
Next are icon-only, so
previous-labelandnext-labelare what they are announced by; a step with nowhere to go reports itself unavailable rather than disappearing. - The ellipsis is not announced. It stands for a run that was elided and goes nowhere, so it is not a control and leaves no node. What tells a reader where in the range they are is the landmark’s item count and each button’s own index.
- One tab stop per destination. The pager is a set of independent destinations rather than one control with a selection inside it, so every live button in it is its own stop and Enter or Space activates the focused one. That is deliberately not the single roving stop Tabs and NavigationMenu use: a roving stop belongs to one control whose arrows move a selection inside it.