Skip to content
ShineUI
Install

Paginator

Pages, ranges and row counts.

<sui-paginator>DataStable

About

Page controls for a list the host pages itself: a range label, first, previous, numbered, next and last buttons, and a rows-per-page select. It sits under sui-data-table in its footer slot, or under any paged list. The nav is named "Pagination", by the host's aria-label, or after a named table; the current page carries aria-current="page".

Usage

<sui-paginator aria-label="Pagination, Basic" total-records="143" page="3" page-size="10"></sui-paginator> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-paginator"); el.pageSizes = []; /* number[] */ el.addEventListener("suiPageChange", (e) => console.log(e.detail)); </script>
import { SuiPaginator } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { const pageSizes = []; /* number[] */ return <SuiPaginator aria-label="Pagination, Basic" totalRecords="143" page="3" pageSize="10" pageSizes={pageSizes} onSuiPageChange={(e) => console.log(e.detail)} />; }
<script setup> import { SuiPaginator } from "@shineui/vue"; import "@shineui/core/shineui.css"; const pageSizes = []; // number[] const onChange = (e) => console.log(e.detail); </script> <template> <SuiPaginator aria-label="Pagination, Basic" total-records="143" page="3" page-size="10" :page-sizes="pageSizes" @sui-page-change="onChange" /> </template>
import { Component } from "@angular/core"; import { SuiPaginator } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiPaginator], template: `<sui-paginator aria-label="Pagination, Basic" total-records="143" page-size="10" [(page)]="page" [pageSizes]="pageSizes"></sui-paginator>`, }) export class Example { page = 1; pageSizes = []; // number[] }

Basic

Seven number slots once the list is long, so the arrows never move as the page changes. The controls are native buttons in a nav labeled Pagination, and the range is a live region, so Next page announces the new range.

<sui-paginator aria-label="Pagination, Basic" total-records="143" page="3" page-size="10"></sui-paginator>

Short lists

Seven pages or fewer and the truncation goes away. First and Previous disable on page 1, Next and Last on the final page.

<sui-paginator aria-label="Pagination, Short lists" total-records="55" page-size="10"></sui-paginator>

One page

The paginator hides while every record fits on one page and no smaller rows-per-page choice would make a second, so a short list or a search that found nothing shows no strip of dead arrows. show-on-single-page keeps it, as here; the button turns the default back on.

<div style="display:grid;gap:10px"> <sui-paginator aria-label="Pagination, One page" id="pg-one" total-records="6" page-size="10" show-on-single-page></sui-paginator> <div><sui-button id="pg-one-hide" variant="outline" size="sm" label="Hide on one page"></sui-button></div> </div>
// setup(root): properties and events setup(root) { const el = root.querySelector('#pg-one'); const btn = root.querySelector('#pg-one-hide'); btn.addEventListener('click', () => { el.showOnSinglePage = !el.showOnSinglePage; btn.label = el.showOnSinglePage ? 'Hide on one page' : 'Keep on one page'; }); }

Page sizes

pageSizes is a property that fills the rows-per-page select; picking a size returns to page 1. hide-page-size drops the select for a footer with no room for it.

<div style="display:grid;gap:12px"> <sui-paginator aria-label="Pagination, Page sizes" id="pg-sizes" total-records="143" page-size="25"></sui-paginator> <sui-paginator aria-label="Pagination, Page sizes without the select" total-records="143" page="2" page-size="10" hide-page-size></sui-paginator> </div>
// setup(root): properties and events setup(root) { root.querySelector('#pg-sizes').pageSizes = [25, 50, 100]; }

Events

suiPageChange carries the new page and suiPageSizeChange the new size; both props are written before either fires. Neither bubbles, so a table or a view around the paginator hears only its own. A totalRecords that shrinks clamps the page and reports that as a change of its own.

page 3 · 10 per page · first 20
<div style="display:grid;gap:10px"> <code id="pg-out" style="font:460 12px/1.6 var(--font-sans);color:var(--ink-muted)">page 3 · 10 per page · first 20</code> <sui-paginator aria-label="Pagination, Events" id="pg-live" total-records="143" page="3" page-size="10"></sui-paginator> <div><sui-button id="pg-shrink" variant="outline" size="sm" label="Filter to 12 rows"></sui-button></div> </div>
// setup(root): properties and events setup(root) { const out = root.querySelector('#pg-out'); const el = root.querySelector('#pg-live'); const btn = root.querySelector('#pg-shrink'); const show = () => { out.textContent = `page ${el.page} · ${el.pageSize} per page · first ${(el.page - 1) * el.pageSize}`; }; el.addEventListener('suiPageChange', show); el.addEventListener('suiPageSizeChange', show); btn.addEventListener('click', () => { const shrunk = el.totalRecords === 12; el.totalRecords = shrunk ? 143 : 12; btn.label = shrunk ? 'Filter to 12 rows' : 'Show all 143 rows'; }); }

Keyboard

KeyAction
TabMoves through the controls in document order: first page, previous page, each page number, next page, last page, then the rows-per-page select when it is shown. A disabled end is skipped, as any disabled button is.
Enter SpaceActivates the focused page control and emits suiPageChange with the new page. The current page's own button changes nothing and emits nothing.
ArrowDown ArrowUpOn the rows-per-page control, which is a sui-select, opens the list and moves through the sizes; Enter picks one, which emits suiPageSizeChange with the new size, then suiPageChange when the paginator went back to page 1.

API

<sui-paginator>

Properties

PropertyAttributeTypeDefaultDescription
hidePageSizehide-page-sizebooleanfalseDrops the rows-per-page select, a size="sm" sui-select whose own suiChange stops inside the paginator, so a form or a listener around the paginator never takes it for a field's.
pagepagenumber1Current page, 1-based. Mutable; clamped to the page count.
pageSizepage-sizenumber10Rows per page. Mutable; changing it resets page to 1.
pageSizesproperty onlynumber[][10, 25, 50, 100]Choices for the rows-per-page select. Property, not attribute.
showOnSinglePageshow-on-single-pagebooleanfalseKeeps the paginator while every record fits on one page. Without it the paginator hides then, unless a smaller rows-per-page choice would make a second page, so a short list or a search that matched nothing shows no "1–6 of 6" strip of dead arrows. A sui-data-table or sui-data-view it sits in drops its footer bar with it when the paginator is all the bar holds.
totalRecordstotal-recordsnumbernumber | undefinedTotal record count, shown in the range label ("21–30 of 143") in tabular numerals; the same name sui-data-table and sui-data-view carry, and what they write here. The range is a polite live region, so activating "Next page" announces the new one.

Events

EventDetailReactVueDescription
suiPageChangenumberonSuiPageChange@sui-page-changeFires when the page changes, by a page control or by a clamp after totalRecords or pageSize shrank the page count; detail is the new page. Does not bubble, so a table or a view the paginator sits in never hears it as its own.
suiPageSizeChangenumberonSuiPageSizeChange@sui-page-size-changeFires when the rows-per-page select changes the page size; detail is the new pageSize. The paginator writes pageSize and returns to page 1 first, then fires this and, when the page moved, suiPageChange. Does not bubble.

Methods

MethodSignatureDescription
setFocus()setFocus() => Promise<void>Focuses the current page's button, or the first page control that takes focus. el.focus() on the host does the same.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also