# Pagination

## Usage

`Pagination` splits a long list into pages. People move with the page buttons or the previous and next controls.

Pass the total and bind the current page:

```vue
<script setup lang="ts">
import { Pagination } from '@astryx-vue/core/Pagination'
import { ref } from 'vue'

const page = ref(1)
const total = ref(100)
</script>

<template>
  <Pagination v-model:page="page" :total="total" />
</template>
```

`pageSize` is how many items each page holds. `showSizeChanger` lets people change that. `showTotal` shows how many items exist and which range the current page covers.

The visible labels are English unless the app calls `provideAstryxLocale('zh-CN')`.

### Anatomy

- **Pagination**: Navigation landmark containing the current paging controls and optional page-size selector.
- **Page-size selector**: Optional Selector for choosing how many items appear per page.
- **First/last buttons**: Optional Button controls that jump to the first or last known page.
- **Previous/next buttons**: Button controls that move backward or forward through the pages.
- **Page number button**: Button for one directly selectable page in the numbered presentation.
- **Ellipsis**: Visual omission marker between non-adjacent page-number buttons.
- **Count readout**: Text showing the current item range and total item count.
- **Compact readout**: Text showing the current page and total page count.
- **Dot**: Page indicator control that reflects and changes the active page.
- **Page input label**: Leading visible label for the editable page-number field.
- **Page input**: NumberInput used to enter a page directly.
- **Page input total**: Trailing text showing the known total page count.

## Best practices

| Guideline | Practice |
| --- | --- |
| Do | Use it under tables and lists that are too long for one page. |
| Do | Offer `showSizeChanger` when the list is large enough that page length matters. |
| Do | Show the total and the current range so people know how much data there is. |
| Don't | Rely on a long row of page buttons on a narrow screen. Keep the control compact. |

## Examples

### Dots carousel

Dot pagination steps through a short set of review cards, where position matters more than a page number.

```vue
<script setup lang="ts">
import { Avatar } from '@astryx-vue/core/Avatar'
import { Card } from '@astryx-vue/core/Card'
import { Icon } from '@astryx-vue/core/Icon'
import { Pagination } from '@astryx-vue/core/Pagination'
import { HStack, VStack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
import { ref } from 'vue'

const reviews = [
  {
    name: 'Jeannie Grant',
    date: 'June 01, 2025',
    stars: 5,
    quote: 'A thorough report was done on our financial situation. Better deals were found and processed on our behalf, which took a lot of stress away.',
  },
  {
    name: 'Derval Russell',
    date: 'November 09, 2025',
    stars: 5,
    quote: 'I have been a client for 8 years now and have always found the advice provided excellent. They take the time to explain things clearly.',
  },
  {
    name: 'Claire Dawson',
    date: 'October 15, 2025',
    stars: 5,
    quote: 'Constantly professional and concise. Our mortgage process was smooth from start to finish thanks to their dedicated team.',
  },
  {
    name: 'Marcus Webb',
    date: 'September 22, 2025',
    stars: 4,
    quote: 'Great service overall. The team was responsive and knowledgeable. Would definitely recommend to anyone looking for solid financial advice.',
  },
]

const page = ref(1)
</script>

<template>
  <VStack :gap="3" style="width: 100%; max-width: 480px">
    <Card :padding="5">
      <VStack :gap="3">
        <HStack :gap="0">
          <Icon
            v-for="star in reviews[page - 1].stars"
            :key="star"
            color="warning"
            icon="i-carbon-star-filled"
            size="sm"
          />
        </HStack>
        <Text type="body">
          {{ reviews[page - 1].quote }}
        </Text>
        <HStack align="center" :gap="3" h-align="start">
          <Avatar :name="reviews[page - 1].name" size="md" />
          <VStack :gap="0">
            <Text type="supporting" weight="bold">
              {{ reviews[page - 1].name }}
            </Text>
            <Text color="secondary" type="supporting">
              {{ reviews[page - 1].date }}
            </Text>
          </VStack>
        </HStack>
      </VStack>
    </Card>
    <Pagination
      v-model:page="page"
      :total-pages="reviews.length"
      variant="dots"
      style="justify-content: center; padding-top: 4px"
    />
  </VStack>
</template>
```

### Page size selector

Pagination sits under a transactions table, with a control for how many rows each page holds.

```vue
<script setup lang="ts">
import { Heading } from '@astryx-vue/core/Heading'
import { Pagination } from '@astryx-vue/core/Pagination'
import { VStack } from '@astryx-vue/core/Stack'
import { Table } from '@astryx-vue/core/Table'
import { ref } from 'vue'

const rows = [
  { id: '1', date: 'Apr 18', description: 'Payment received', amount: '$2,450.00' },
  { id: '2', date: 'Apr 15', description: 'Subscription renewal', amount: '$99.00' },
  { id: '3', date: 'Apr 12', description: 'Refund issued', amount: '-$180.00' },
  { id: '4', date: 'Apr 10', description: 'Invoice paid', amount: '$1,200.00' },
]

const columns = [
  { key: 'date', header: 'Date' },
  { key: 'description', header: 'Description' },
  { key: 'amount', header: 'Amount' },
]

const page = ref(1)
const pageSize = ref(10)
</script>

<template>
  <VStack style="width: 100%; max-width: 500px">
    <Heading :level="4">
      Transactions
    </Heading>
    <Table :columns="columns" :data="rows" id-key="id" />
    <Pagination
      v-model:page="page"
      v-model:page-size="pageSize"
      show-size-changer
      :page-size-options="[10, 25, 50, 100]"
      :total="350"
      variant="count"
      style="padding-top: 8px; flex-direction: row-reverse"
    />
  </VStack>
</template>
```

### With table

The count variant under a table, at the small size, shows the item range for the current page.

```vue
<script setup lang="ts">
import { Pagination } from '@astryx-vue/core/Pagination'
import { VStack } from '@astryx-vue/core/Stack'
import { Table } from '@astryx-vue/core/Table'
import { computed, ref } from 'vue'

const allRows = [
  { id: '1', name: 'Olivia Chen', role: 'Engineer', status: 'Active' },
  { id: '2', name: 'Marcus Rivera', role: 'Designer', status: 'Active' },
  { id: '3', name: 'Aisha Patel', role: 'Marketing', status: 'Invited' },
  { id: '4', name: 'James Okafor', role: 'Engineer', status: 'Active' },
  { id: '5', name: 'Sofia Nguyen', role: 'Sales', status: 'Active' },
  { id: '6', name: 'Liam Johansson', role: 'Engineer', status: 'Inactive' },
  { id: '7', name: 'Elena Kowalski', role: 'Designer', status: 'Active' },
  { id: '8', name: 'David Kim', role: 'Marketing', status: 'Active' },
  { id: '9', name: 'Priya Sharma', role: 'Sales', status: 'Invited' },
  { id: '10', name: 'Noah Tanaka', role: 'Engineer', status: 'Active' },
  { id: '11', name: 'Fatima Al-Rashid', role: 'Designer', status: 'Active' },
  { id: '12', name: 'Carlos Mendez', role: 'Marketing', status: 'Inactive' },
]

const columns = [
  { key: 'name', header: 'Name' },
  { key: 'role', header: 'Role' },
  { key: 'status', header: 'Status' },
]

const pageSize = 4
const page = ref(1)
const pageRows = computed(() => allRows.slice((page.value - 1) * pageSize, page.value * pageSize))
</script>

<template>
  <VStack style="width: 100%; max-width: 500px">
    <Table :columns="columns" :data="pageRows" id-key="id" />
    <Pagination
      v-model:page="page"
      :page-size="pageSize"
      size="sm"
      :total="allRows.length"
      variant="count"
      style="justify-content: center; padding-top: 8px"
    />
  </VStack>
</template>
```

### Page input

Type a page number to jump. First and last sit outside the previous and next buttons. When the total is unknown, pass hasMore instead of a last page.

```vue
<script setup lang="ts">
import { Pagination } from '@astryx-vue/core/Pagination'
import { ref } from 'vue'

const page = ref(3)
</script>

<template>
  <Pagination
    v-model:page="page"
    :page-size="10"
    :total="240"
    variant="input"
  />
</template>
```

## Pagination Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `page` | `number` | `1` | Current page, starting at 1. |
| `pageSize` | `number` | `10` | Items per page. |
| `total` | `number` | `0` | Total number of items. |
| `totalItems` | `number` | — | Total number of items, the original name for `total`. Wins over `total` and `totalPages` when both are set. |
| `label` | `string` | `Pagination` / `分页` | Accessible name of the navigation landmark. |
| `showSizeChanger` | `boolean` | `false` | Shows the page-size selector. |
| `pageSizeOptions` | `number[]` | `[10, 20, 50, 100]` | Page-size choices. |
| `showTotal` | `boolean` | `false` | Shows the total count. |
| `isDisabled` | `boolean` | `false` | Disables the control. |
| `disabled` | `boolean` | `false` | Older name of `isDisabled`. Either one disables the control. |
| `prevLabel` | `string` | `Previous` / `上一页` | Previous-page label. English by default; Chinese when `zh-CN` is provided. |
| `nextLabel` | `string` | `Next` / `下一页` | Next-page label. English by default; Chinese when `zh-CN` is provided. |
| `pageSizeLabel` | `string` | `Per page` / `每页` | Label for the page-size selector. English by default; Chinese when `zh-CN` is provided. |
| `variant` | `'pages' \| 'count' \| 'compact' \| 'dots' \| 'input' \| 'none'` | `'pages'` | Middle of the control. `input` is an editable page field. |
| `siblingCount` | `number` | `1` | How many page buttons sit on each side of the current page. Narrow containers drop another step. |
| `step` | `number` | `1` | How many pages previous and next move. |
| `hasMore` | `boolean` | — | Whether another page exists when the total is unknown. A `total` above 0 wins. |
| `pageLabel` | `string` | `Page` / `页` | Noun in front of the `input` field. |
| `hasFirstLast` | `boolean` | `input` when the page count is known | Shows buttons that jump to the first and last page. |
| `changeAction` | `(page: number) => void \| Promise<void>` | — | Runs after the page is committed. The control stays busy until it settles, and a failure does not roll the page back. |

## Pagination events

| Name | Payload | Description |
| --- | --- | --- |
| `update:page` | `page: number` | Emitted when the page changes. |
| `update:pageSize` | `pageSize: number` | Emitted when the page size changes. |
| `pageSizeChange` | `pageSize: number` | Emitted when the page size changes, before the page returns to the first one. |
| `change` | `page: number, pageSize: number` | Emitted when the page or page size changes. |

## Pagination slots

| Name | Props | Description |
| --- | --- | --- |
| `total` | `{ total, from, to }` | Custom total label. |
