{
  "title": "usePagination",
  "description": "Headless pagination math — sibling/boundary windowing, prev/next/first/last actions, controlled or uncontrolled state.",
  "url": "/docs/hooks/use-pagination",
  "since": "0.0.7",
  "tags": [
    "hooks",
    "pagination"
  ],
  "platform": "both",
  "source": "---\ntitle: usePagination\ndescription: Headless pagination math — sibling/boundary windowing, prev/next/first/last actions, controlled or uncontrolled state.\nsince: 0.0.7\ntags: [hooks, pagination]\nplatform: both\ncategory: hooks\n---\n\nimport { Preview } from '@/components/preview';\n\nA pure-logic hook that produces the items to render and the actions that drive them — without any DOM or React Native elements. Use it directly when [`<Pagination>`](/docs/components/pagination)'s default UI doesn't fit (drag scrubbers, infinite-scroll page indicators, custom keyboard interactions, integration with virtualized lists).\n\nThe math mirrors MUI's `usePagination` algorithm so its behavior is predictable for anyone who has used the React ecosystem before.\n\n## Usage\n\n```tsx\nimport { usePagination } from '@nori-ui/core/client';\n\nfunction MyPaginator() {\n    const p = usePagination({ pageCount: 12, defaultPage: 1 });\n    return (\n        <div>\n            <button type=\"button\" disabled={!p.canPrev} onClick={p.prev}>Prev</button>\n            {p.pages.map((item, i) => {\n                if (item.type === 'ellipsis') return <span key={i}>…</span>;\n                if (item.type !== 'page' || item.page === undefined) return null;\n                const target = item.page;\n                return (\n                    <button\n                        type=\"button\"\n                        key={target}\n                        disabled={item.selected}\n                        onClick={() => p.goToPage(target)}\n                    >\n                        {target}\n                    </button>\n                );\n            })}\n            <button type=\"button\" disabled={!p.canNext} onClick={p.next}>Next</button>\n        </div>\n    );\n}\n```\n\n## Live demo\n\n<Preview name=\"pagination-hook\" />\n\n## API\n\n### Arguments\n\n| Name | Type | Default | Description |\n|---|---|---|---|\n| `page` | `number` | — | Controlled current page (1-indexed). Omit for uncontrolled. |\n| `defaultPage` | `number` | `1` | Initial page when uncontrolled (1-indexed). |\n| `pageCount` | `number` | — | Total number of pages. **Required.** |\n| `siblingCount` | `number` | `1` | Pages on each side of the current page. |\n| `boundaryCount` | `number` | `1` | Pages always visible at start / end. |\n| `showFirstLast` | `boolean` | `false` | Include `'first'` / `'last'` items in the `pages` list. |\n| `showPrevNext` | `boolean` | `true` | Include `'prev'` / `'next'` items in the `pages` list. |\n| `onPageChange` | `(page: number) => void` | — | Fired on every page change. |\n\n### Returns\n\n| Name | Type | Description |\n|---|---|---|\n| `page` | `number` | Current page (1-indexed), clamped to `[1, max(1, pageCount)]`. |\n| `pages` | `ReadonlyArray<PaginationItemDescriptor>` | Item descriptors in render order. Each is one of `{ type: 'page', page, selected }`, `{ type: 'ellipsis' }`, or `{ type: 'prev' \\| 'next' \\| 'first' \\| 'last', disabled }`. |\n| `canPrev` | `boolean` | `true` when `page > 1`. |\n| `canNext` | `boolean` | `true` when `page < pageCount`. |\n| `goToPage` | `(page: number) => void` | Jump to a specific 1-indexed page (clamped). |\n| `prev` | `() => void` | Decrement by 1 (clamped at 1). |\n| `next` | `() => void` | Increment by 1 (clamped at `pageCount`). |\n| `first` | `() => void` | Jump to page 1. |\n| `last` | `() => void` | Jump to `pageCount`. |\n\n### Item descriptor\n\n```ts\ntype PaginationItemDescriptor = {\n    type: 'page' | 'prev' | 'next' | 'first' | 'last' | 'ellipsis';\n    page?: number;          // set on `'page'` items\n    selected?: boolean;     // set on the current `'page'` item\n    disabled?: boolean;     // set on prev/next/first/last when at the edge\n};\n```\n\n## Behavior notes\n\n- **Controlled vs uncontrolled** — pass `page` to control externally, or `defaultPage` to seed the initial state. The hook switches modes automatically.\n- **Out-of-range pages** are clamped to `[1, pageCount]`, so an over-eager parent can't crash the renderer.\n- **`pageCount` of 0** is treated as `1` (a single empty page) so the hook never returns an empty `pages` array.\n- The `pages` array length is `O(siblingCount + boundaryCount)` — it does **not** scale with `pageCount`. Even at 1,000,000 pages the rendered window is constant-time.\n\n## Related\n\n- [`<Pagination>`](/docs/components/pagination) — the full UI built on this hook.\n"
}
