Choosing a mode
Pagination is one nav with three ways to drive it. Pick by who owns the content.
- Auto — all items are already in the DOM and NDS slices them into pages. Wrap them in
nds-paged-content, mark each withnds-page-item, and point the nav at the wrapper withdata-auto-pagination="id"(see Paginated Grid / Table below). - Manual / server — each page is a separate URL or request. Use a plain
nds-paginationnav with no content classes; for AJAX, listen fornds:pagination:changeand load the page yourself (see Standard Pagination below). - Data-driven — you know the page count but want NDS to build the controls. Set
data-total-pages="N"on an empty nav (see Data-Driven Pagination below).
nds-paged-content and nds-page-item belong to auto only — manual and data-driven navs never use them.
Standard Pagination
Numbered page controls with previous and next arrows for stepping through content
<nav class="nds-pagination" aria-label="Pagination">
<ul class="nds-pagination-list">
<li class="nds-pagination-item nds-pagination-prev">
<button type="button" class="nds-btn nds-subtle nds-icon-only" aria-label="Previous page" disabled>
<i class="nds-icon nds-hgi-arrow-prev-01" aria-hidden="true"></i>
</button>
</li>
<li class="nds-pagination-item">
<button type="button" class="nds-btn nds-subtle nds-indicator" data-state="active" aria-current="page" aria-label="Page 1">
<span class="nds-label">1</span>
</button>
</li>
<li class="nds-pagination-item">
<button type="button" class="nds-btn nds-subtle nds-indicator" aria-label="Page 2">
<span class="nds-label">2</span>
</button>
</li>
<li class="nds-pagination-item">
<button type="button" class="nds-btn nds-subtle nds-indicator" aria-label="Page 3">
<span class="nds-label">3</span>
</button>
</li>
<li class="nds-pagination-item">
<button type="button" class="nds-btn nds-subtle nds-indicator" aria-label="Page 4">
<span class="nds-label">4</span>
</button>
</li>
<li class="nds-pagination-item">
<button type="button" class="nds-btn nds-subtle nds-indicator" aria-label="Page 5">
<span class="nds-label">5</span>
</button>
</li>
<li class="nds-pagination-item nds-pagination-next">
<button type="button" class="nds-btn nds-subtle nds-icon-only" aria-label="Next page">
<i class="nds-icon nds-hgi-arrow-next-01" aria-hidden="true"></i>
</button>
</li>
</ul>
</nav>
Data-Driven Pagination
Set a page count and optional active page on an empty nav element, and the component builds the numbered controls automatically. By default they are buttons you wire through the change event (SPA); add a data-page-url template to render navigable links instead, for no-JS full-reload server pagination
<nav class="nds-pagination" data-total-pages="8" data-active-page="1" aria-label="Pagination"></nav>
Large Page Counts
Totals in the hundreds or thousands need no special handling: the page dropdown opens centered on the current page, renders pages on demand as you scroll, and offers a jump field, so type a page number and press Enter to go straight there
<nav class="nds-pagination" data-total-pages="2000" data-active-page="1000" aria-label="Pagination"></nav>
Automatic Ellipsis Collapse
When page count exceeds five, middle pages collapse into a dropdown for compact navigation. Write the full list of pages flat and the component folds them on its own, re-folding as pages are added or removed. Very large ranges stay fast: the dropdown opens centered on the current page, renders pages on demand as you scroll, and adds a jump-to-page field
Paginated Grid
Automatically split grid items across pages by marking children with nds-page-item and adding a data-auto-pagination="id" nav that targets the container by id
Paginated Table
Table rows paginate the same way as grid items, with the nds-page-item class on each body row
|
#
|
Service
|
Description |
System
|
Most Used |
|---|---|---|---|---|
| 1 | Identity Verification | Verify your national identity and obtain digital certificates for government transactions | Identity & Records | Yes |
| 2 | Passport Renewal | Renew your passport online with expedited processing and home delivery options | Identity & Records | Yes |
| 3 | Birth Certificate Request | Request official birth certificates and family documentation online | Identity & Records | — |
| 4 | Marriage Contract Registration | Register marriage contracts and obtain official marriage certificates | Identity & Records | — |
| 5 | Driver's License Services | Apply for, renew, or update your driving license information | Transport & Vehicles | Yes |
| 6 | Vehicle Registration | Register new vehicles, transfer ownership, or renew your vehicle registration | Transport & Vehicles | Yes |
| 7 | Visa Application | Apply for entry visas for visitors, workers, or family members | Transport & Vehicles | — |
| 8 | Health Insurance Enrollment | Enroll in government health insurance plans and manage your coverage | Healthcare & Social | Yes |
| 9 | Medical Appointment Booking | Schedule appointments at government hospitals and healthcare facilities | Healthcare & Social | Yes |
| 10 | Employment Certificate | Request official employment certificates and salary statements | Healthcare & Social | Yes |
| 11 | Work Permit Processing | Apply for work permits and employment authorization for foreign workers | Healthcare & Social | — |
| 12 | Retirement Benefits Application | Apply for retirement pensions and manage your social security benefits | Healthcare & Social | — |
| 13 | Tax Declaration Filing | Submit your annual tax returns and manage tax obligations online | Business & Finance | Yes |
| 14 | VAT Registration | Register for VAT and manage value-added tax compliance for your business | Business & Finance | — |
| 15 | Business License Application | Apply for commercial licenses and business permits for new ventures | Business & Finance | — |
| 16 | Property Registration | Register property ownership, transfers, and real estate transactions | Business & Finance | — |
| 17 | Building Permit Request | Apply for construction permits and building approvals for new projects | Business & Finance | — |
| 18 | School Enrollment | Enroll children in public schools and manage educational records | Education & Justice | Yes |
| 19 | Certificate Authentication | Authenticate academic certificates and professional credentials | Education & Justice | — |
| 20 | Court Case Filing | File legal cases and track court proceedings through the judicial system | Education & Justice | — |
Records Counter
A "Showing x to y of z" line that pagination keeps live. Point any element at the paged container with data-paged-target="id" and mark number slots inside it: the sentence, language, and emphasis stay yours, only the numbers are stamped. With an active Filter, the count is the filtered count automatically
<!-- Author the sentence; pagination stamps the numbers. Prerender the real
initial values so the line is correct before JS loads. -->
<span class="nds-bar-text" data-paged-target="pagination_records_demo">
Showing <b data-paged-from>1</b>–<b data-paged-to>4</b> of <b data-paged-count>9</b> items
</span>
<div id="pagination_records_demo" class="nds-paged-content nds-grid"
style="--per-page: 4; --max-col: 4; --mid-col: 3; --min-col: 2;">
<div class="nds-page-item nds-card nds-stroke">Card 1</div>
<div class="nds-page-item nds-card nds-stroke">Card 2</div>
<div class="nds-page-item nds-card nds-stroke">Card 3</div>
<div class="nds-page-item nds-card nds-stroke">Card 4</div>
<div class="nds-page-item nds-card nds-stroke">Card 5</div>
<div class="nds-page-item nds-card nds-stroke">Card 6</div>
<div class="nds-page-item nds-card nds-stroke">Card 7</div>
<div class="nds-page-item nds-card nds-stroke">Card 8</div>
<div class="nds-page-item nds-card nds-stroke">Card 9</div>
</div>
<nav class="nds-pagination" data-auto-pagination="pagination_records_demo" aria-label="Pagination"></nav>
Per-page Picker
Any nds-dropmenu in select mode (data-select-name) becomes a records-per-page control by adding data-per-page-target="id". Each item's data-value is the new --per-page. Pagination re-slices the target on selection — same id-ref pattern as data-auto-pagination. For server-side navs, listen to nds:dropmenu:selected and drive your own fetch with NDS.Pagination.updateRecords + setTotalPages
<!-- Standard dropmenu SELECT MODE + data-per-page-target="id". Set the
container's inline --per-page to match data-select-value so the first
paint agrees with the trigger label. -->
<div class="nds-toolbar">
<div class="nds-bar-start">
<div class="nds-dropmenu"
data-select-name="perPage"
data-select-value="6"
data-per-page-target="pagination_perpage_demo">
<button class="nds-btn nds-secondary-outline nds-md nds-menu-btn nds-dropmenu-trigger" type="button">
<span class="nds-label">6</span>
</button>
<div class="nds-dropmenu-menu nds-center" hidden>
<div class="nds-dropmenu-scroll">
<button class="nds-btn nds-subtle nds-dropmenu-item" data-value="6"><span class="nds-label">6</span></button>
<button class="nds-btn nds-subtle nds-dropmenu-item" data-value="12"><span class="nds-label">12</span></button>
<button class="nds-btn nds-subtle nds-dropmenu-item" data-value="24"><span class="nds-label">24</span></button>
</div>
</div>
</div>
</div>
<div class="nds-bar-end">
<span class="nds-bar-text" data-paged-target="pagination_perpage_demo">
Showing <b data-paged-from>1</b>–<b data-paged-to>6</b> of <b data-paged-count>24</b> items
</span>
</div>
</div>
<div id="pagination_perpage_demo" class="nds-paged-content nds-grid" style="--per-page: 6;">
<div class="nds-page-item nds-card nds-stroke">Item 1</div>
<!-- … -->
</div>
<nav class="nds-pagination" data-auto-pagination="pagination_perpage_demo" aria-label="Pagination"></nav>
Built-in Features
Scans for nds-pagination elements on the page. Active states, disabled buttons, and ellipsis collapse configure themselves from the markup.
Paginations with more than five pages collapse middle pages into a dropdown menu, keeping the first three and last page visible. The collapse is live: add or remove page buttons and the nav re-collapses or expands to match.
The page dropdown opens centered on the current page and renders pages on demand as you scroll, so thousands of pages open as fast as ten. Ranges past 30 pages add a jump field: type a number and press Enter to go straight there.
Point a nds-pagination nav at a nds-paged-content container with data-auto-pagination="id" and the pagination controls, page visibility, and scroll behavior are handled automatically.
Add or remove nds-page-item elements at runtime and the pages recalculate automatically, keeping the current page, at any nesting including table rows. Filtered content stays in sync with no manual call.
Buttons shrink on mobile viewports and content pagination watches for --per-page changes on resize, rebuilding pages to match the new layout.
Opt in with data-page-param and the current page persists in the URL: reloads and shared links land on the same page, composing with filter and sort parameters.
Navigate to any page, refresh after content changes, or create new pagination instances through the NDS.Pagination API.
Usage Guidelines
Best Practices
- Use content pagination (
data-auto-pagination) for items already in the DOM: card grids, table rows, or list items - Use manual pagination when each page number links to a different URL or triggers a server-side request
- Use data-driven generation (
data-total-pages) when you know the page count but want the component to build the controls. Default controls are buttons you drive throughnds:pagination:change(SPA); adddata-page-url="?page={page}"to emit navigable links for no-JS full-reload server pagination. For a runtime count change, callNDS.Pagination.setTotalPages() - Do not paginate fewer than two pages. Auto-pagination hides controls automatically when all items fit
- Set
--per-pageto match your grid column count so each page fills the layout. Update it in media queries for responsive grids - For tables, put
nds-paged-contenton the<tbody>itself and addnds-page-itemon each<tr>. A wrapper around the table would be hidden until pagination initializes; the tbody shows its skeleton rows instead - Both
<button>and<a>elements work inside pagination items. Use buttons for client-side navigation, anchors for distinct URLs - Bind a nav to its content by id —
data-auto-pagination="gridId"matching the wrapper'sid(the same convention as filter'sdata-filter-target). Omit the value to bind the immediately-preceding wrapper instead - Keep
nds-page-item(your content items) distinct fromnds-pagination-item(the nav's<li>controls) — similar names, opposite roles - For server or AJAX pagination, listen for
nds:pagination:change(itsdetail.pageis the resolved page), load that page, then callNDS.Pagination.setPage()to highlight it (calling it once the response lands also scrolls the content back into view, with no delay to guess). When a new query changes the total page count, callNDS.Pagination.setTotalPages()to rebuild the controls (it keeps the current page; pass a page number to jump). NDS owns the nav UI, you own the data - Attach your logic to the nav, not to individual page buttons: listen for
nds:pagination:changeor give each page a distinct<a href>. Collapsing rebuilds the page buttons and dropdown pages render on demand, so custom classes,data-*attributes,href, and inlineonclicksurvive on author-written markup, but a listener added withaddEventListeneron a button does not - For content pagination, adding or removing
nds-page-itemelements re-paginates automatically and keeps the current page, no re-init needed, at any nesting (cards, list items, table rows). A wholesale content swap that injects a brand-new nav (e.g. a filter's AJAX HTML mode) still needsNDS.Pagination.reinit(), or have the server return it already paginated - Don't paginate a continuously growing feed. Use Scroll More for load-on-scroll content where the total count isn't fixed
- Don't use pagination for a linear, must-finish-in-order flow like a form wizard. Reach for Stepper instead
Modifier Classes
| Class | Applied to | Description |
|---|---|---|
nds-md |
.nds-pagination |
Medium size, 32px buttons |
nds-sm |
.nds-pagination |
Small size, 24px buttons |
nds-paged-content |
Content wrapper | Marks a container whose nds-page-item children should be paginated. On a table it goes on the <tbody>, not a wrapper |
nds-page-item |
Content children | Marks individual content items (cards, rows, list items) to be paginated. Not to be confused with nds-pagination-item below |
nds-pagination-list |
Nav | The <ul> inside .nds-pagination that holds the page controls |
nds-pagination-item |
Nav children | An <li> control in the nav (a page number or prev/next) — the navigation counterpart to nds-page-item |
Data Attributes
| Attribute | Description |
|---|---|
data-auto-pagination="id" |
Add to nds-pagination to auto-paginate the nds-paged-content container with that id. The value is optional — omit it to bind the immediately-preceding container instead |
data-total-pages="N" |
Set on an empty nds-pagination to auto-generate N page controls. Optionally add data-active-page="N" for the initial active page (read once at init; defaults to 1). For a count that changes at runtime, call NDS.Pagination.setTotalPages() |
data-page-url="?page={page}" |
Pair with data-total-pages to render the controls as navigable <a href> links instead of buttons: {page} is replaced with each page number. Use for no-JS, full-reload server pagination (the nav re-renders on each navigation). Without it the controls are buttons you wire through nds:pagination:change |
data-page-param |
Add to nds-pagination to sync the current page with a URL query parameter, the same way filter and sort own theirs. Named page by default; set a value like data-page-param="gpage" to rename it when several navs share a page. Read once at init (clamped to the page count, applied without firing events); page changes write it back and page 1 removes it |
data-state="active" |
Set on a page button to mark it as the current page. Updated automatically on navigation |
data-pagination-no-scroll |
Add to nds-pagination to stop a page change from scrolling the content back into view. Scrolling is on by default and already skips itself when the content sits below the sticky nav. Opt out when you want to decide per page change, then call NDS.Pagination.scrollToContent() yourself |
data-paged-target="id" |
Set on any element to make it a records counter for the paged container with that id. Inside it, pagination stamps the current window and count into [data-paged-from], [data-paged-to], and [data-paged-count] slots, with thousand separators. Auto-pagination only; for server pagination stamp the slots via NDS.Pagination.updateRecords() |
CSS Custom Properties
| Property | Default | Description |
|---|---|---|
--pagination-margin-top |
var(--spacing-2xl) |
Space above the pagination nav |
--per-page |
5 |
Items per page in auto-pagination. Set on the nds-paged-content container |
--pagination-scroll-offset |
120 |
Gap in px between the sticky nav and the content top when a page change scrolls it into view. Set on the nds-pagination nav |