# Pagination

> Show rows a page at a time with a pager and an optional rows-per-page choice, instead of one long scroll.

The grid scrolls through any number of rows without paging, since only the rows on screen are drawn. When pages suit the task better, such as a report someone works through or a page number to refer to, `pageSize` shows rows a page at a time with a pager.

Move through the 120 rows with the pager, or pick another page size; sorting and filtering apply to all rows, not just the page:

Example: A paged grid (pagination.svelte)

```svelte
<script lang="ts">
  import { Grid, type ColumnDef } from 'bo-grid';
  import { quotes } from './data';

  const columns: ColumnDef[] = [
    { type: 'number', key: 'id', header: '#', decimals: 0, width: 70 },
    { type: 'text', key: 'symbol', header: 'Symbol', width: 100 },
    { type: 'price', key: 'price', header: 'Price', width: 96 },
    { type: 'percent', key: 'change', header: 'Chg %', width: 96 },
    { type: 'volume', key: 'volume', header: 'Volume', flex: 1 },
  ];

  // 120 rows: ten copies of the quotes, each with its own id.
  let rows = $state(
    Array.from({ length: 10 }, (_, n) => quotes().map((q) => ({ ...q, id: n * 12 + q.id, price: q.price + n }))).flat(),
  );
</script>

<Grid {rows} {columns} pageSize={10} pageSizeOptions={[10, 25, 50]} height={360} />
```

data.ts:

```ts
// Small, fixed data for the docs' examples: every example reads the same
// symbols, so a reader compares features, not datasets.
// A type, not an interface: it then fits GridRow's index signature.
export type Quote = {
  id: number;
  symbol: string;
  name: string;
  sector: string;
  price: number;
  change: number;
  volume: number;
};

const BASE: Omit<Quote, 'id'>[] = [
  { symbol: 'VNM', name: 'Vinamilk', sector: 'Consumer', price: 68.5, change: 1.2, volume: 1_204_000 },
  { symbol: 'FPT', name: 'FPT Corp', sector: 'Technology', price: 132, change: -0.4, volume: 860_500 },
  { symbol: 'HPG', name: 'Hoa Phat', sector: 'Materials', price: 27.35, change: 0.74, volume: 3_412_900 },
  { symbol: 'VCB', name: 'Vietcombank', sector: 'Financials', price: 91.5, change: 0.55, volume: 1_986_200 },
  { symbol: 'MWG', name: 'Mobile World', sector: 'Consumer', price: 51.4, change: -1.1, volume: 2_250_400 },
  { symbol: 'SSI', name: 'SSI Securities', sector: 'Financials', price: 33.3, change: -0.45, volume: 4_104_700 },
  { symbol: 'VIC', name: 'Vingroup', sector: 'Real estate', price: 44.15, change: -0.11, volume: 1_530_100 },
  { symbol: 'GAS', name: 'PV Gas', sector: 'Energy', price: 71.8, change: 0.3, volume: 645_800 },
  { symbol: 'MSN', name: 'Masan', sector: 'Consumer', price: 76.2, change: 2.05, volume: 1_120_300 },
  { symbol: 'TCB', name: 'Techcombank', sector: 'Financials', price: 23.9, change: 0.84, volume: 5_870_000 },
  { symbol: 'VHM', name: 'Vinhomes', sector: 'Real estate', price: 41.6, change: -0.72, volume: 2_904_600 },
  { symbol: 'POW', name: 'PV Power', sector: 'Energy', price: 12.25, change: 1.66, volume: 6_315_200 },
];

/** A fresh copy of the quotes, so one example's edits never reach another. */
export function quotes(): Quote[] {
  return BASE.map((q, i) => ({ id: i + 1, ...q }));
}
```

## Turning it on

```svelte
<Grid {rows} {columns} pageSize={25} pageSizeOptions={[25, 50, 100]} height={640} />
```

- `pageSize` above 0 shows a pager with first, previous, next and last buttons. Rows still virtualize within a page.
- `pageSizeOptions` adds a rows-per-page choice to the pager. Picking one goes back to the first page, and `onPageSizeChange` reports the new size.
- Pages apply to rows in memory; a server [data source](https://bonguynvan.github.io/bo-grid/docs/server-data/) already loads rows a window at a time as the user scrolls.

## Controlling the page

The current page is the grid's own by default. To drive it, for example from the URL, pass `page` (0-based) and update it from `onPageChange`:

```svelte
<script lang="ts">
  let page = $state(0);
</script>

<Grid {rows} {columns} pageSize={25} {page} onPageChange={(p) => (page = p)} height={480} />
```

The page size works the same way: it changes on its own from the dropdown, unless you also drive `pageSize` and update it from `onPageSizeChange`.

## Reference

- `pageSize`: `number` (default `0`) — Rows per page. When > 0 (in-memory mode), shows a pager instead of one long scroll; rows still virtualize within a page. `0` turns paging off.
- `pageSizeOptions`: `number[]` (default `undefined`) — Page-size choices for a dropdown in the pager. Picking one re-pages from page 0. Uncontrolled unless you also drive `pageSize` and update it from `onPageSizeChange`.

  ```svelte
  <Grid {rows} {columns} height={480} pageSize={50} pageSizeOptions={[25, 50, 100]} />
  ```

- `onPageSizeChange`: `(size: number) => void` — Called with the new page size when the pager's size dropdown changes.

  ```svelte
  <Grid {rows} {columns} height={480} {pageSize} pageSizeOptions={[25, 50, 100]} onPageSizeChange={(n) => (pageSize = n)} />
  ```

- `page`: `number` (default `undefined`) — Controlled current page (0-based). Omit for uncontrolled paging.
- `onPageChange`: `(page: number) => void` — Called with the new page index when the pager is used.

  ```svelte
  <Grid {rows} {columns} height={480} pageSize={50} {page} onPageChange={(p) => (page = p)} />
  ```

---
Page: https://bonguynvan.github.io/bo-grid/docs/pagination/ · All pages: https://bonguynvan.github.io/bo-grid/llms.txt
