---
name: pagination
title: Pagination
url: https://ui.frontic.com/components/ui/pagination.md
tier: ui
install: npx @frontic/ui add pagination
files:
  - components/ui/pagination/Pagination.vue
  - components/ui/pagination/PaginationContent.vue
  - components/ui/pagination/PaginationEllipsis.vue
  - components/ui/pagination/PaginationFirst.vue
  - components/ui/pagination/PaginationItem.vue
  - components/ui/pagination/PaginationLast.vue
  - components/ui/pagination/PaginationLink.vue
  - components/ui/pagination/PaginationNext.vue
  - components/ui/pagination/PaginationPrevious.vue
  - components/ui/pagination/index.ts
registryDependencies:
  - button
dependencies:
  - "@lucide/vue"
  - "@vueuse/core"
  - reka-ui
---

# Pagination

Lets the shopper move through numbered pages of results, with Previous and Next and, when there are many pages, the first and last page around an ellipsis. The current page is marked from the pagination's own page. In a container narrower than 28rem only the current page stays between Previous and Next, which drop their labels below 24rem.

## Install

```sh
npx @frontic/ui add pagination
```

Built from: [Button](https://ui.frontic.com/components/ui/button.md)

Used by: [Recent Orders](https://ui.frontic.com/components/sections/recent-orders.md), [Category Grid](https://ui.frontic.com/components/templates/category-grid.md)

npm packages: `@lucide/vue`, `@vueuse/core`, `reka-ui`

## Examples

```vue
<script setup lang="ts">
import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationNext, PaginationPrevious } from '@/components/ui/pagination'
</script>

<template>
  <div class="w-2xl max-w-full">
    <Pagination :items-per-page="10" :total="100" :default-page="4" show-edges>
      <PaginationContent v-slot="{ items }">
        <PaginationPrevious />
        <template v-for="(item, index) in items" :key="index">
          <PaginationItem v-if="item.type === 'page'" :value="item.value">{{ item.value }}</PaginationItem>
          <PaginationEllipsis v-else :index="index" />
        </template>
        <PaginationNext />
      </PaginationContent>
    </Pagination>
  </div>
</template>
```

## Anatomy

```vue
<Pagination>
  <PaginationContent>
    <PaginationPrevious />
    <PaginationItem />
    <PaginationEllipsis />
    <PaginationNext />
  </PaginationContent>
</Pagination>
```

## API reference

### Pagination

Groups all parts of the pagination. Renders a `<nav>`.

`data-slot="pagination"` · `Pagination.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `page` | `number` |  | The controlled value of the current page. Can be binded as `v-model:page`. |
| `defaultPage` | `number` | `1` | The value of the page that should be active when initially rendered.<br><br>Use when you do not need to control the value state. |
| `itemsPerPage` (required) | `number` |  | Number of items per page |
| `total` | `number` | `0` | Number of items in your list |
| `siblingCount` | `number` | `2` | Number of sibling should be shown around the current page |
| `disabled` | `boolean` | `false` | When `true`, prevents the user from interacting with item |
| `showEdges` | `boolean` | `false` | When `true`, always show first page, last page, and ellipsis |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'nav'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:page` | `value: number` | Event handler called when the page value changes |

| Slot | Slot props | Description |
| --- | --- | --- |
| `default` | `{ page: number; pageCount: number; }` | Holds the pagination's content; gets the current page and the number of pages. |

### PaginationContent

Lists the page numbers, ellipses and navigation buttons of the pagination.

`data-slot="pagination-content"` · `PaginationContent.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` |  | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Slot | Slot props | Description |
| --- | --- | --- |
| `default` | `{ items: ({ type: "ellipsis"; } \| { type: "page"; value: number; })[]; }` | Holds the page buttons; gets the items to render, each a page with its number or an ellipsis. |

### PaginationEllipsis

Marks pages that are skipped between the shown page numbers.

`data-slot="pagination-ellipsis"` · `PaginationEllipsis.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` |  | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `label` | `string` | `'More pages'` | Read by screen readers in place of the ellipsis icon. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-type` | `'ellipsis'` | What the item is: a `page` link or an `ellipsis`. |

### PaginationFirst

Lets the user jump to the first page. Renders a `<button>`.

`data-slot="pagination-first"` · `PaginationFirst.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | `'md'` | The button's size, from Button's size scale. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `label` | `string` | `'First'` | Shown next to the icon on the button that jumps to the first page; hidden when the pagination is narrow. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | Carries the `size` prop. |

### PaginationItem

Shows one page number in the list that the user can go to. Renders a `<button>`.

`data-slot="pagination-item"` · `PaginationItem.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` (required) | `number` |  | Value for the page |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | `'icon-md'` | The button's size, from Button's size scale. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `isActive` | `boolean` |  | Marks the page as the current one with an outlined button; follows the pagination's page when left out. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-active` |  | Present while the item is the active one. |
| `data-selected` |  | Present while the item is selected. |
| `data-size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | Carries the `size` prop. |
| `data-type` | `'page'` | What the item is: a `page` link or an `ellipsis`. |
| `data-variant` | `'outline' \| 'ghost'` | The variant the part is drawn with. |

### PaginationLast

Lets the user jump to the last page. Renders a `<button>`.

`data-slot="pagination-last"` · `PaginationLast.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | `'md'` | The button's size, from Button's size scale. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `label` | `string` | `'Last'` | Shown next to the icon on the button that jumps to the last page; hidden when the pagination is narrow. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | Carries the `size` prop. |

### PaginationLink

Links to a specific page and marks it when it is the current one. Renders a `<a>`.

`data-slot="pagination-link"` · `PaginationLink.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'a'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `href` | `string` |  | The URL of the page this link opens. |
| `size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | `'icon-md'` | The button's size, from Button's size scale. |
| `isActive` | `boolean` | `false` | Marks the current page with an outlined button and `aria-current="page"`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-active` |  | Present on the link to the current page. |
| `data-size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | Carries the `size` prop. |
| `data-variant` | `'outline' \| 'ghost'` | The variant the part is drawn with. |

### PaginationNext

Lets the user go to the next page. Renders a `<button>`.

`data-slot="pagination-next"` · `PaginationNext.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | `'md'` | The button's size, from Button's size scale. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `label` | `string` | `'Next'` | Shown next to the icon on the button that goes to the next page; hidden when the pagination is narrow. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | Carries the `size` prop. |

### PaginationPrevious

Lets the user go to the previous page. Renders a `<button>`.

`data-slot="pagination-previous"` · `PaginationPrevious.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | `'md'` | The button's size, from Button's size scale. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `label` | `string` | `'Previous'` | Shown next to the icon on the button that goes to the previous page; hidden when the pagination is narrow. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | Carries the `size` prop. |

## Exports

```ts
import { type PaginationButtonSize } from '@/components/ui/pagination'
```

- `PaginationButtonSize`: `InstanceType<typeof Button>['$props']['size']`

## Styling

| Part | Recipe |
| --- | --- |
| `previous` | [`pagination-previous`](https://ui.frontic.com/DESIGN.md#pagination-previous) |
| `next` | [`pagination-next`](https://ui.frontic.com/DESIGN.md#pagination-next) |
| `content` | [`pagination-content`](https://ui.frontic.com/DESIGN.md#pagination-content) |
| `default` | [`pagination`](https://ui.frontic.com/DESIGN.md#pagination) |
| `ellipsis` | [`pagination-ellipsis`](https://ui.frontic.com/DESIGN.md#pagination-ellipsis) |

## Source

`components/ui/pagination/Pagination.vue`

```vue
<script lang="ts">
/** Groups all parts of the pagination. */
export default {}
</script>

<script setup lang="ts">
import type { PaginationRootEmits, PaginationRootProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { PaginationRoot, useForwardPropsEmits } from 'reka-ui'

const props = defineProps<PaginationRootProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<PaginationRootEmits>()

const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits)

defineSlots<{
  /** Holds the pagination's content; gets the current page and the number of pages. */
  default?: (props: { page: number; pageCount: number }) => unknown
}>()
</script>

<template>
  <PaginationRoot v-slot="slotProps" data-slot="pagination" v-bind="forwarded" :class="['fui-pagination @container/pagination', props.class]">
    <slot v-bind="slotProps" />
  </PaginationRoot>
</template>
```

`components/ui/pagination/PaginationContent.vue`

```vue
<script lang="ts">
/** Lists the page numbers, ellipses and navigation buttons of the pagination. */
export default {}
</script>

<script setup lang="ts">
import type { PaginationListProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { PaginationList, useForwardProps } from 'reka-ui'

const props = defineProps<PaginationListProps & { class?: HTMLAttributes['class'] }>()

const delegatedProps = reactiveOmit(props, 'class')

defineSlots<{
  /** Holds the page buttons; gets the items to render, each a page with its number or an ellipsis. */
  default?: (props: { items: ({ type: 'ellipsis' } | { type: 'page'; value: number })[] }) => unknown
}>()
const forwarded = useForwardProps(delegatedProps)
</script>

<template>
  <PaginationList v-slot="slotProps" data-slot="pagination-content" v-bind="forwarded" :class="['fui-pagination-content', props.class]">
    <slot v-bind="slotProps" />
  </PaginationList>
</template>
```

`components/ui/pagination/PaginationEllipsis.vue`

```vue
<script lang="ts">
/** Marks pages that are skipped between the shown page numbers. */
export default {}
</script>

<script setup lang="ts">
import { MoreHorizontalIcon } from '@lucide/vue'

import type { PaginationEllipsisProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { PaginationEllipsis, useForwardProps } from 'reka-ui'

const props = withDefaults(
  defineProps<
    PaginationEllipsisProps & {
      class?: HTMLAttributes['class']
      /** Read by screen readers in place of the ellipsis icon. */
      label?: string
    }
  >(),
  {
    label: 'More pages',
  },
)

const delegatedProps = reactiveOmit(props, 'class', 'label')
const forwarded = useForwardProps(delegatedProps)
</script>

<template>
  <PaginationEllipsis data-slot="pagination-ellipsis" v-bind="forwarded" :class="['fui-pagination-ellipsis @max-md/pagination:hidden', props.class]">
    <slot>
      <MoreHorizontalIcon data-icon />
      <span class="sr-only">{{ props.label }}</span>
    </slot>
  </PaginationEllipsis>
</template>
```

`components/ui/pagination/PaginationFirst.vue`

```vue
<script lang="ts">
/** Lets the user jump to the first page. */
export default {}
</script>

<script setup lang="ts">
import { ChevronsLeftIcon } from '@lucide/vue'

import type { PaginationFirstProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { PaginationButtonSize } from '.'
import { reactiveOmit } from '@vueuse/core'
import { PaginationFirst, useForwardProps } from 'reka-ui'
import { Button } from '@library/components/ui/button'

const props = withDefaults(
  defineProps<
    PaginationFirstProps & {
      /** The button's size, from Button's size scale. */
      size?: PaginationButtonSize
      class?: HTMLAttributes['class']
      /** Shown next to the icon on the button that jumps to the first page; hidden when the pagination is narrow. */
      label?: string
    }
  >(),
  {
    size: 'md',
    label: 'First',
  },
)

const delegatedProps = reactiveOmit(props, 'class', 'size', 'label')
const forwarded = useForwardProps(delegatedProps)
</script>

<template>
  <PaginationFirst data-slot="pagination-first" v-bind="forwarded" as-child>
    <Button variant="ghost" color="neutral" :size="props.size" :class="[props.class]">
      <slot>
        <ChevronsLeftIcon class="rtl:-scale-x-100" data-icon />
        <span class="hidden @sm/pagination:block">{{ props.label }}</span>
      </slot>
    </Button>
  </PaginationFirst>
</template>
```

`components/ui/pagination/PaginationItem.vue`

```vue
<script lang="ts">
/** Shows one page number in the list that the user can go to. */
export default {}
</script>

<script setup lang="ts">
import type { PaginationListItemProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { PaginationButtonSize } from '.'
import { reactiveOmit } from '@vueuse/core'
import { injectPaginationRootContext, PaginationListItem, useForwardProps } from 'reka-ui'
import { computed } from 'vue'
import { Button } from '@library/components/ui/button'

const props = withDefaults(
  defineProps<
    PaginationListItemProps & {
      /** The button's size, from Button's size scale. */
      size?: PaginationButtonSize
      class?: HTMLAttributes['class']
      /** Marks the page as the current one with an outlined button; follows the pagination's page when left out. */
      isActive?: boolean
    }
  >(),
  {
    size: 'icon-md',
    isActive: undefined,
  },
)

const delegatedProps = reactiveOmit(props, 'class', 'size', 'isActive')
const root = injectPaginationRootContext()
const active = computed(() => props.isActive ?? root.page.value === props.value)
const forwarded = useForwardProps(delegatedProps)
</script>

<template>
  <PaginationListItem data-slot="pagination-item" v-bind="forwarded" as-child>
    <Button :variant="active ? 'outline' : 'ghost'" color="neutral" :size="props.size" :data-active="active ? '' : undefined" :class="['@max-md/pagination:not-data-active:hidden', props.class]">
      <slot />
    </Button>
  </PaginationListItem>
</template>
```

`components/ui/pagination/PaginationLast.vue`

```vue
<script lang="ts">
/** Lets the user jump to the last page. */
export default {}
</script>

<script setup lang="ts">
import { ChevronsRightIcon } from '@lucide/vue'

import type { PaginationLastProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { PaginationButtonSize } from '.'
import { reactiveOmit } from '@vueuse/core'
import { PaginationLast, useForwardProps } from 'reka-ui'
import { Button } from '@library/components/ui/button'

const props = withDefaults(
  defineProps<
    PaginationLastProps & {
      /** The button's size, from Button's size scale. */
      size?: PaginationButtonSize
      class?: HTMLAttributes['class']
      /** Shown next to the icon on the button that jumps to the last page; hidden when the pagination is narrow. */
      label?: string
    }
  >(),
  {
    size: 'md',
    label: 'Last',
  },
)

const delegatedProps = reactiveOmit(props, 'class', 'size', 'label')
const forwarded = useForwardProps(delegatedProps)
</script>

<template>
  <PaginationLast data-slot="pagination-last" v-bind="forwarded" as-child>
    <Button variant="ghost" color="neutral" :size="props.size" :class="[props.class]">
      <slot>
        <span class="hidden @sm/pagination:block">{{ props.label }}</span>
        <ChevronsRightIcon class="rtl:-scale-x-100" data-icon />
      </slot>
    </Button>
  </PaginationLast>
</template>
```

`components/ui/pagination/PaginationLink.vue`

```vue
<script lang="ts">
/** Links to a specific page and marks it when it is the current one. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { PaginationButtonSize } from '.'
import { Button } from '@library/components/ui/button'

const props = withDefaults(
  defineProps<
    Pick<PrimitiveProps, 'as'> & {
      /** The URL of the page this link opens. */
      href?: string
      /** The button's size, from Button's size scale. */
      size?: PaginationButtonSize
      /** Marks the current page with an outlined button and `aria-current="page"`. */
      isActive?: boolean
      class?: HTMLAttributes['class']
    }
  >(),
  {
    as: 'a',
    size: 'icon-md',
    isActive: false,
  },
)
</script>

<template>
  <Button
    :as="props.as"
    :href="props.href"
    data-slot="pagination-link"
    :data-active="props.isActive ? '' : undefined"
    :aria-current="props.isActive ? 'page' : undefined"
    :variant="props.isActive ? 'outline' : 'ghost'"
    color="neutral"
    :size="props.size"
    :class="props.class"
  >
    <slot />
  </Button>
</template>
```

`components/ui/pagination/PaginationNext.vue`

```vue
<script lang="ts">
/** Lets the user go to the next page. */
export default {}
</script>

<script setup lang="ts">
import { ChevronRightIcon } from '@lucide/vue'

import type { PaginationNextProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { PaginationButtonSize } from '.'
import { reactiveOmit } from '@vueuse/core'
import { PaginationNext, useForwardProps } from 'reka-ui'
import { Button } from '@library/components/ui/button'

const props = withDefaults(
  defineProps<
    PaginationNextProps & {
      /** The button's size, from Button's size scale. */
      size?: PaginationButtonSize
      class?: HTMLAttributes['class']
      /** Shown next to the icon on the button that goes to the next page; hidden when the pagination is narrow. */
      label?: string
    }
  >(),
  {
    size: 'md',
    label: 'Next',
  },
)

const delegatedProps = reactiveOmit(props, 'class', 'size', 'label')
const forwarded = useForwardProps(delegatedProps)
</script>

<template>
  <PaginationNext data-slot="pagination-next" v-bind="forwarded" as-child>
    <Button variant="ghost" color="neutral" :size="props.size" :class="['fui-pagination-next', props.class]">
      <slot>
        <span class="hidden @sm/pagination:block">{{ props.label }}</span>
        <ChevronRightIcon class="rtl:-scale-x-100" data-icon />
      </slot>
    </Button>
  </PaginationNext>
</template>
```

`components/ui/pagination/PaginationPrevious.vue`

```vue
<script lang="ts">
/** Lets the user go to the previous page. */
export default {}
</script>

<script setup lang="ts">
import { ChevronLeftIcon } from '@lucide/vue'

import type { PaginationPrevProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { PaginationButtonSize } from '.'
import { reactiveOmit } from '@vueuse/core'
import { PaginationPrev, useForwardProps } from 'reka-ui'
import { Button } from '@library/components/ui/button'

const props = withDefaults(
  defineProps<
    PaginationPrevProps & {
      /** The button's size, from Button's size scale. */
      size?: PaginationButtonSize
      class?: HTMLAttributes['class']
      /** Shown next to the icon on the button that goes to the previous page; hidden when the pagination is narrow. */
      label?: string
    }
  >(),
  {
    size: 'md',
    label: 'Previous',
  },
)

const delegatedProps = reactiveOmit(props, 'class', 'size', 'label')
const forwarded = useForwardProps(delegatedProps)
</script>

<template>
  <PaginationPrev data-slot="pagination-previous" v-bind="forwarded" as-child>
    <Button variant="ghost" color="neutral" :size="props.size" :class="['fui-pagination-previous', props.class]">
      <slot>
        <ChevronLeftIcon class="rtl:-scale-x-100" data-icon />
        <span class="hidden @sm/pagination:block">{{ props.label }}</span>
      </slot>
    </Button>
  </PaginationPrev>
</template>
```

`components/ui/pagination/index.ts`

```ts
import type { Button } from '@library/components/ui/button'

export { default as Pagination } from './Pagination.vue'
export { default as PaginationContent } from './PaginationContent.vue'
export { default as PaginationEllipsis } from './PaginationEllipsis.vue'
export { default as PaginationFirst } from './PaginationFirst.vue'
export { default as PaginationItem } from './PaginationItem.vue'
export { default as PaginationLast } from './PaginationLast.vue'
export { default as PaginationLink } from './PaginationLink.vue'
export { default as PaginationNext } from './PaginationNext.vue'
export { default as PaginationPrevious } from './PaginationPrevious.vue'

export type PaginationButtonSize = InstanceType<typeof Button>['$props']['size']
```
