block
A composed block for navigating paginated data. Combines first/previous/next/last navigation buttons with a page picker select and an optional items-per-page selector. Navigation buttons default to icon-only with visually hidden labels for accessibility.
Built on the framework-agnostic @pindoba/core-pagination (connectPagination) headless core and available for both Svelte and Astro. It also powers the Table footer. The Astro component is interactive out of the box (it self-syncs and emits a pindoba:change event); for full client control call mountPagination.
On page 1
---
import Pagination from "../pagination.astro";
---
<div data-demo="pagination-default">
<Pagination itemsTotal={120} itemsPerPage={10} />
<p data-pagination-readout aria-live="polite" style="margin-top: 0.75rem;">
On page 1
</p>
</div>
<script>
// The component is interactive out of the box (it self-syncs disabled state,
// the summary, and the selects). It also emits `pindoba:change` so consumers
// can react — here we reflect the current page into a readout.
function boot() {
const root = document.querySelector<HTMLElement>(
'[data-demo="pagination-default"]',
);
if (!root) return;
const readout = root.querySelector<HTMLElement>(
"[data-pagination-readout]",
);
root.addEventListener("pindoba:change", (event) => {
const detail = (event as CustomEvent).detail as {
page: number;
itemsPerPage: number;
};
if (readout) {
readout.textContent = `On page ${detail.page + 1} (showing ${detail.itemsPerPage} per page)`;
}
});
}
boot();
document.addEventListener("astro:page-load", boot);
</script>Show text labels alongside icons on the navigation buttons by setting visuallyHidden: false on any label key. This is useful when space allows or when a more explicit UI is preferred.
import { useState } from "react";
import { Pagination } from "../Pagination";
export default function LabelsDemo() {
const [page, setPage] = useState(0);
const [itemsPerPage, setItemsPerPage] = useState(10);
return (
<Pagination
itemsTotal={100}
itemsPerPage={itemsPerPage}
page={page}
onChangePage={(next) => setPage(next)}
onChangeItemsPerPage={(next) => setItemsPerPage(next)}
labels={{
first: { visuallyHidden: false },
previous: { visuallyHidden: false },
next: { visuallyHidden: false },
last: { visuallyHidden: false },
}}
/>
);
}"secondary"Emphasis for the navigation buttons.
Stable id used to associate `<label>`s with their `<select>`s.
10Items shown per page. The items-per-page selector offers `pageSizeOptions` filtered to those ≤ `itemsTotal`. Supports two-way binding.
Total item count across all pages. Drives the "X – Y / Z" summary and, when `pageCount` is omitted, the derived page count + items-per-page capping.
Override the text or visibility for each navigation label. Each key accepts `text` to change the copy and `visuallyHidden` to toggle screen-reader-only vs visible. Navigation buttons default to `visuallyHidden: true` (icon-only with `aria-label`).
No description yet.
No description yet.
No description yet.
No description yet.
No description yet.
No description yet.
Current page index. 0-based by default — set `zeroBased={false}` for 1-based indexing. Supports two-way binding.
Explicit total page count. Wins over the value derived from `itemsTotal` / `itemsPerPage` — a host with server-side pagination (only one page of rows in memory) passes its known total here.
[10, 20, 50, 100]Options offered by the items-per-page select.
Per-slot escape hatch. Container slots accept `style` (merged into the slot class) and `props`; control slots forward `props` onto the child Button/Select. See the PassThrough docs.
"sm"Layout scale; also mirrored onto the child Button/Select `size`.
trueWhether `page` uses 0-based indexing (page 0 = first page). When `false`, uses 1-based indexing (page 1 = first page).
Plus all standard <div> HTML
attributes.