Components · Navigation
Pagination
A page selector for a paged list or table — prev / numbered pages with ellipsis / next; two sizes, a simple mode, and a whole-control disabled state.
Examples
Size
Mode
Paged list · 248 items · 20 per page · page 3 (ellipsis for the long range)
Short range · no ellipsis
Small size
Simple mode · prev · n / total · next
Disabled (whole control)
<!-- Paste-and-run: save as .html and open in a browser. No build step.
<aha-pagination> is the SAME shared custom element React and Vue consume — here in its
native form. Theming comes only from the --aha-* tokens in tokens.css. -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/tokens.css">
<script type="module">
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-pagination.js'; // registers <aha-pagination> (+ <aha-icon>)
</script>
<!-- total + page-size (the page count is total ÷ page-size); ellipsis appears for long ranges -->
<aha-pagination id="pager" total="248" page-size="20" current="3"></aha-pagination>
<!-- pages="12" — pass a direct page count instead of total+page-size -->
<aha-pagination pages="12" current="3"></aha-pagination>
<!-- size="small" — the compact 24px scale for dense tables -->
<aha-pagination pages="12" current="3" size="small"></aha-pagination>
<!-- simple — prev · n / total · next, for tight widths -->
<aha-pagination pages="12" current="3" simple></aha-pagination>
<!-- disabled — the whole control -->
<aha-pagination pages="12" current="3" disabled></aha-pagination>
<script>
// `current` reflects as it changes; each step emits a composed `change` event.
document.getElementById('pager').addEventListener('change', (e) => console.log('page', e.detail.page));
</script>
import '@ahaslides-product/design/aha-pagination'; // registers <aha-pagination> (+ <aha-icon>)
import { useRef, useEffect } from 'react';
// React 18 needs a thin wrapper for the composed `change` event; React 19 can bind onChange directly.
function AhaPagination({ total, pageSize, pages, current, size, simple, disabled, onChange }) {
const ref = useRef();
useEffect(() => {
const el = ref.current;
const h = (e) => onChange?.(e.detail.page);
el.addEventListener('change', h);
return () => el.removeEventListener('change', h);
});
return (
<aha-pagination
ref={ref}
total={total}
page-size={pageSize}
pages={pages}
current={current}
size={size} // 'default' | 'small'
simple={simple ? '' : undefined}
disabled={disabled ? '' : undefined}
/>
);
}
// usage — total+page-size (or pass pages instead), size, simple, disabled
<AhaPagination total={248} pageSize={20} current={page} onChange={setPage} />
<AhaPagination pages={12} current={page} size="small" onChange={setPage} />
<AhaPagination pages={12} current={page} simple onChange={setPage} />
// main.ts — register the element + mark aha-* as custom elements
import '@ahaslides-product/design/aha-pagination';
app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('aha-');
// Component.vue
<script setup>
import { ref } from 'vue';
const page = ref(3);
function onChange(e) { page.value = e.detail.page; }
</script>
<template>
<!-- total + page-size (the page count is total ÷ page-size) -->
<aha-pagination :total="248" :page-size="20" :current="page" @change="onChange" />
<!-- pages="12" — a direct page count; size="small" | simple | disabled -->
<aha-pagination :pages="12" :current="page" size="small" @change="onChange" />
<aha-pagination :pages="12" :current="page" simple @change="onChange" />
<aha-pagination :pages="12" :current="page" disabled @change="onChange" />
</template>
API
| Prop | Type | Default | Notes |
|---|---|---|---|
total | number | 0 | Total item count across all pages (used with page-size) |
page-size | number | 10 | Items per page; total ÷ page-size decides the page count |
pages | number | — | Direct page count; wins over total+page-size when set |
current | number | 1 | The active page (1-based); reflected as it changes |
size | 'default' | 'small' | default | Cell scale: 32px square (default) or compact 24px (small) |
simple | boolean | false | Collapse the numbers to prev · n / total · next |
disabled | boolean | false | Disable the whole control (every cell not-allowed) |
change | CustomEvent | — | Composed event on page change, detail { page } |
Install
# .npmrc — once: point the @ahaslides-product scope at GitHub Packages
@ahaslides-product:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} # a GitHub token with read:packages
npm i @ahaslides-product/design
import '@ahaslides-product/design/tokens.css'; // once, at the app root
import '@ahaslides-product/design/aha-pagination'; // registers <aha-pagination>
Agent feed for this component (absolute, fetchable anywhere): pagination.agent.json · pagination.md · pagination.llms.txt
When to use
When to use
- Pagination — a long list or table is split into pages the user steps through
- infinite scroll — a feed the user grazes — not a set they navigate by position
- Load more — occasional appends where exact page position doesn't matter
Show pagination only when there is more than one page. Keep page cells on the 32px square + radius-8 scale (or the 24px small scale for dense tables), and reserve the brand fill for the current page. The first/last ellipsis keeps long ranges compact; reach for simple mode when width is tight.
Surfaces
dashboard admin editor
Spec
Cell (default) min-width 32 · height 32 · radius 8 · SemiBold 600 · Cell (small) min-width 24 · height 24 · radius 6 · 12px · Rest white bg, 1px border #E3E3E3, text-default · Hover border-hover #D3B4FF + color-primary text (motion-fast + ease-out) · Current color-primary #6A1EBB fill, text-inverse #FFFFFF · Ellipsis … text-tertiary #8A8A8A, aria-hidden, for elided ranges · Simple prev · <b>n</b> / total · next — n in color-primary · Disabled prev/next text-disabled #B5B5B5, not-allowed · Disabled (all) every cell not-allowed; current keeps brand fill at .55 opacity · Arrows <aha-icon name="system-caret-left|right" size=16>