๐ŸŸข Pagination

  • Enables quick access to the first or last page.
  • Allows the option to show edges constantly or not.

Basic

NPagination is used to divide content into pages by displaying a subset of data at a time. Please refer to the Radix-ui pagination for more API information.

PropTypeDefaultDescription
totalnumber0The total number of items in your list.
pagenumber-The value that controls the current page and can be bound with v-model.
itemsPerPagenumber10The number of items displayed per page.
showEdgesbooleanfalseWhen set to true, the first page, last page, and ellipsis will always be displayed.
disabledbooleanfalseDisables pagination functionality.
<script setup lang="ts">
const currentPage = ref(1)
</script>

<template>
  <div class="flex">
    <NPagination
      v-model:page="currentPage"
      :total="100"
      show-edges
    />
  </div>
</template>

Visibility

PropTypeDefaultDescription
showFirstbooleantrueDisplays the first page button.
showLastbooleantrueDisplays the last page button.
showPrevbooleantrueDisplays the previous page button.
showNextbooleantrueDisplays the next page button.
showListItembooleantrueDisplays the list items.
Current Page: 1
<script setup lang="ts">
const currentPage = ref(1)
</script>

<template>
  <div class="flex flex-col space-y-4">
    <span>Current Page: {{ currentPage }}</span>

    <NPagination
      v-model:page="currentPage"
      :total="100"
      :show-list-item="false"
    />
  </div>
</template>

Size

PropDefaultDescription
sizesmAdjusts the size of the entire pagination.
_pagination-first.sizesmCustomizes the size of the first page button.
_pagination-last.sizesmCustomizes the size of the last page button.
_pagination-prev.sizesmCustomizes the size of the previous page button.
_pagination-next.sizesmCustomizes the size of the next page button.
_pagination-list-item.sizesmCustomizes the size of the page list items.
_pagination-ellipsis.sizesmCustomizes the size of the ellipsis indicator.

๐Ÿš€ You can freely adjust the size of the pagination using any size imaginable. No limits exist, and you aan use breakpoints such as sm:sm, xs:lg to change size based on screen size or states such as hover:lg, focus:3xl to change size based on input state and more.

The height and width of the pagination scale depends on the size. If you want to change the height and width simultaneously, you can always customize it using utility classes or you can use the square prop.
<script lang="ts" setup>
const currentPage = ref(1)
</script>

<template>
  <div class="flex flex-col space-y-4">
    <NPagination
      v-model:page="currentPage"
      :total="100"
      size="xs"
    />

    <NPagination
      v-model:page="currentPage"
      :total="100"
      size="sm"
    />

    <NPagination
      v-model:page="currentPage"
      :total="100"
      size="md"
    />

    <NPagination
      v-model:page="currentPage"
      :total="100"
      size="lg"
    />
  </div>
</template>

Sibling Count

PropTypeDefaultDescription
siblingCountnumber2The number of surrounding pages displayed around the current page.
<script lang="ts" setup>
const currentPage = ref(1)
</script>

<template>
  <div class="flex flex-col space-y-4">
    <NPagination
      v-model:page="currentPage"
      :total="300"
      :sibling-count="3"
    />
    <NPagination
      v-model:page="currentPage"
      :total="300"
      :sibling-count="1"
    />
  </div>
</template>

Variant and Color

PropTypeDefaultDescription
pagination-selected{variant}-{color}solid-primaryThe color of the selected page.
pagination-unselected{variant}-{color}solid-whiteThe color of the unselected page.
pagination-ellipsis{variant}-{color}text-blackThe color of the ellipsis.
Some NPagination subcomponents are wrapped around the NButton component. This means that all the props and slots of NButton are available. Please refer to the Props section for more information.
<script lang="ts" setup>
const currentPage = ref(1)
</script>

<template>
  <div class="flex flex-col">
    <NPagination
      v-model:page="currentPage"
      :total="100"
      pagination-selected="solid-black"
      pagination-unselected="ghost-gray"
      show-edges
    />

    <NSeparator />

    <NPagination
      v-model:page="currentPage"
      :total="100"
      pagination-selected="soft-primary"
      pagination-unselected="link-primary"
      pagination-ellipsis="text-primary"
      show-edges
    />

    <NSeparator />

    <NPagination
      v-model:page="currentPage"
      :total="100"
      pagination-selected="solid-pink"
      pagination-unselected="outline-pink"
      pagination-ellipsis="text-pink"
      show-edges
    />

    <NSeparator />

    <NPagination
      v-model:page="currentPage"
      :total="100"
      pagination-selected="solid-indigo"
      pagination-unselected="soft-indigo"
      pagination-ellipsis="text-indigo"
      show-edges
    />
  </div>
</template>

Rounded

rounded="{size}" - changes the border-radius of the pagination.

๐Ÿš€ You can freely adjust the size of the rounded corners using any size imaginable. There are no limits, and you can use breakpoints such as sm:sm, xs:lg to change size based on screen size or states such as hover:lg, focus:3xl to change size based on input state, and more.

You can use any size provided by the Tailwind CSS border-radius scale; the default is md. You can also add your own sizes to the scale through the Configuration section.
<script lang="ts" setup>
const currentPage = ref(1)
</script>

<template>
  <div class="flex flex-col">
    <NPagination
      v-model:page="currentPage"
      :total="100"
      pagination-selected="solid-white"
      pagination-unselected="ghost-gray"
      rounded="b-2xl"
    />

    <NSeparator />

    <NPagination
      v-model:page="currentPage"
      :total="100"
      pagination-selected="solid-orange"
      pagination-unselected="outline-white"
    />

    <NSeparator />

    <NPagination
      v-model:page="currentPage"
      :total="100"
      pagination-selected="solid-yellow"
      pagination-unselected="outline-white"
      rounded="t-full"
    />
  </div>
</template>

Sub Components

PropDescription
_pagination-list-itemCustomizes the pagination list item component.
_pagination-prevCustomizes the previous page navigation button.
_pagination-nextCustomizes the next page navigation button.
_pagination-firstCustomizes the first page navigation button.
_pagination-lastCustomizes the last page navigation button.
_pagination-ellipsisCustomizes the ellipsis indicator in the pagination.
_pagination-listCustomizes the pagination list component.
For the sub-components' props, please refer to the Props section. Refer to the Radix-ui pagination for more information.
<template>
  <div class="flex items-start">
    <NPagination
      :total="100"
      :show-first="false"
      :show-last="false"
      show-edges
      rounded="none"
      :_pagination-list="{
        class: 'gap-0 divide-x divide-base border-1 border-base',
        rounded: 'r-full l-full',
      }"
      :_pagination-prev="{
        icon: false,
        square: false,
        paginationUnselected: 'ghost-white',
        label: 'Previous',
        leading: 'i-lucide-arrow-left',
      }"
      :_pagination-list-item="{
        paginationUnselected: 'ghost-white',
        paginationSelected: 'solid-black',
      }"
      :_pagination-next="{
        icon: false,
        square: false,
        paginationUnselected: 'ghost-white',
        label: 'Next',
        trailing: 'i-lucide-arrow-right',
      }"
    />
  </div>
</template>

Slots

SlotDescriptionProps
firstCustomizes the first page navigation button.-
lastCustomizes the last page navigation button.-
prevCustomizes the previous page navigation button.-
nextCustomizes the next page navigation button.-
list-itemCustomizes the pagination list item component.item page
ellipsisCustomizes the ellipsis indicator in the pagination.-
<template>
  <div class="flex items-start">
    <NPagination
      :total="100"
      :show-first="false"
      :show-last="false"
      show-edges
    >
      <template #prev>
        <NButton
          btn="ghost-gray"
          label="Previous"
          leading="i-lucide-chevron-left"
        />
      </template>

      <template #list-item="{ item, page }">
        <NPaginationListItem
          :square="false"
          pagination-unselected="ghost-gray"
          pagination-selected="solid-white"
          :value="item.value"
          class="gap-0"
          leading="i-lucide-hash"
          :label="`${item.value}`"
          :page
          :una="{
            btnLeading: 'text-10px',
          }"
        />
      </template>

      <template #ellipsis>
        <NIcon
          name="i-lucide-chevrons-left-right-ellipsis"
          size="xs"
        />
      </template>

      <template #next>
        <NButton
          btn="ghost-gray"
          label="Next"
          trailing="i-lucide-chevron-right"
        />
      </template>
    </NPagination>
  </div>
</template>

Props

import type { PaginationEllipsisProps, PaginationFirstProps, PaginationLastProps, PaginationListItemProps, PaginationListProps, PaginationNextProps, PaginationPrevProps, PaginationRootProps } from 'radix-vue'
import type { HTMLAttributes } from 'vue'
import type { NButtonProps } from './button'

interface BaseExtensionProps {
  square?: HTMLAttributes['class']
  class?: HTMLAttributes['class']
  rounded?: HTMLAttributes['class']
  size?: HTMLAttributes['class']
}

type isVisible = boolean

export interface NPaginationProps extends
  PaginationRootProps,
  BaseExtensionProps,
  Pick<NButtonProps, 'paginationSelected' | 'paginationUnselected'>,
  Pick<NPaginationEllipsisProps, 'paginationEllipsis'> {
  showFirst?: isVisible
  showPrev?: isVisible
  showNext?: isVisible
  showLast?: isVisible
  showListItem?: isVisible

  // sub-components
  _paginationList?: Partial<NPaginationListProps>
  _paginationListItem?: Partial<NPaginationListItemProps>
  _paginationEllipsis?: Partial<NPaginationEllipsisProps>
  _paginationFirst?: Partial<NPaginationFirstProps>
  _paginationPrev?: Partial<NPaginationPrevProps>
  _paginationNext?: Partial<NPaginationNextProps>
  _paginationLast?: Partial<NPaginationLastProps>

  una?: NPaginationUnaProps
}

export interface NPaginationListProps extends PaginationListProps, BaseExtensionProps {
  una?: Pick<NPaginationUnaProps, 'paginationList'>
}

export interface NPaginationListItemProps extends PaginationListItemProps, NButtonProps {
  isSelected?: boolean
  page?: PaginationRootProps['page']
}

export interface NPaginationEllipsisProps extends PaginationEllipsisProps, BaseExtensionProps {
  paginationEllipsis?: HTMLAttributes['class']

  una?: Pick<NPaginationUnaProps, 'paginationEllipsis' | 'paginationEllipsisIconBase' | 'paginationEllipsisIcon'>
}

export interface NPaginationFirstProps extends PaginationFirstProps, NButtonProps {
}

export interface NPaginationPrevProps extends PaginationPrevProps, NButtonProps {
}

export interface NPaginationNextProps extends PaginationNextProps, NButtonProps {
}

export interface NPaginationLastProps extends PaginationLastProps, NButtonProps {
}

interface NPaginationUnaProps {
  paginationRoot?: HTMLAttributes['class']
  paginationList?: HTMLAttributes['class']
  paginationListItem?: HTMLAttributes['class']
  paginationEllipsis?: HTMLAttributes['class']
  paginationEllipsisIconBase?: HTMLAttributes['class']
  paginationEllipsisIcon?: HTMLAttributes['class']
}

Presets

type PaginationPrefix = 'pagination'

export const staticPagination: Record<`${PaginationPrefix}-${string}` | PaginationPrefix, string> = {
  // configurations
  'pagination': 'overflow-hidden',
  'pagination-list': 'flex items-center gap-1 overflow-hidden',

  // components
  'pagination-root': '',
  'pagination-list-item': 'pagination',

  'pagination-ellipsis-base': 'btn flex items-center justify-center',
  'pagination-ellipsis-icon-base': 'w-1em h-1em',
  'pagination-ellipsis-icon': 'i-lucide-ellipsis',

  'pagination-first': 'pagination',
  'pagination-first-icon': 'i-lucide-chevrons-left',

  'pagination-prev': 'pagination',
  'pagination-prev-icon': 'i-lucide-chevron-left',

  'pagination-next': 'pagination',
  'pagination-next-icon': 'i-lucide-chevron-right',

  'pagination-last': 'pagination',
  'pagination-last-icon': 'i-lucide-chevrons-right',
}

export const dynamicPagination = [
  [
    /^pagination-ellipsis(?:-([^-]+))?(?:-([^-]+))?$/,
    ([, variant = 'text', color = 'black']) =>
      `btn-${variant}-${color}`,
  ],

  [
    /^pagination-selected(?:-([^-]+))?(?:-([^-]+))?$/,
    ([, variant = 'solid', color = 'primary']) =>
      `data-[selected=true]:btn-${variant}-${color}`,
  ],
  [
    /^pagination-unselected(?:-([^-]+))?(?:-([^-]+))?$/,
    ([, variant = 'solid', color = 'white']) =>
      `data-[selected=false]:btn-${variant}-${color}`,
  ],

]

export const pagination = [
  ...dynamicPagination,
  staticPagination,
]

Component

<script setup lang="ts">
import type { NPaginationProps } from '../../../types'
import { reactivePick } from '@vueuse/core'
import { PaginationList, PaginationRoot, type PaginationRootEmits, useForwardPropsEmits } from 'radix-vue'
import { cn } from '../../../utils'
import PaginationEllipsis from './PaginationEllipsis.vue'
import PaginationFirst from './PaginationFirst.vue'
import PaginationLast from './PaginationLast.vue'
import PaginationListItem from './PaginationListItem.vue'
import PaginationNext from './PaginationNext.vue'
import PaginationPrev from './PaginationPrev.vue'

const props = withDefaults(defineProps<NPaginationProps>(), {
  showFirst: true,
  showLast: true,
  showListItem: true,
  showNext: true,
  showPrev: true,
})

const emits = defineEmits<PaginationRootEmits>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultPage', 'disabled', 'itemsPerPage', 'page', 'showEdges', 'siblingCount', 'total'), emits)
</script>

<template>
  <PaginationRoot
    v-slot="{ page }"
    v-bind="rootProps"
    :class="cn(
      'pagination-root',
      props.class,
      props.una?.paginationRoot,
    )"
  >
    <PaginationList
      v-slot="{ items }"
      :class="cn(
        'pagination-list',
        props?._paginationList?.class,
        props.una?.paginationList,
      )"
      v-bind="_paginationList"
    >
      <slot>
        <PaginationFirst
          v-if="showFirst"
          :rounded
          :size
          :pagination-selected
          :pagination-unselected
          v-bind="_paginationFirst"
        >
          <slot
            name="first"
          />
        </PaginationFirst>

        <PaginationPrev
          v-if="showPrev"
          :rounded
          :pagination-selected
          :pagination-unselected
          :size
          v-bind="_paginationPrev"
        >
          <slot
            name="prev"
          />
        </PaginationPrev>

        <template v-if="showListItem">
          <template v-for="(item, index) in items">
            <slot
              v-if="item.type === 'page'"
              name="list-item"
              :item="item"
              :page="page"
            >
              <PaginationListItem
                :key="index"
                :value="item.value"
                :page
                :rounded
                :size
                :pagination-selected
                :pagination-unselected
                v-bind="_paginationListItem"
              />
            </slot>

            <PaginationEllipsis
              v-else
              :key="item.type"
              :index="index"
              :rounded
              :size
              :pagination-ellipsis
              :una
              v-bind="_paginationEllipsis"
            >
              <slot
                name="ellipsis"
              />
            </PaginationEllipsis>
          </template>
        </template>

        <PaginationNext
          v-if="showNext"
          :rounded
          :size
          :pagination-selected
          :pagination-unselected
          v-bind="_paginationNext"
        >
          <slot
            name="next"
          />
        </PaginationNext>

        <PaginationLast
          v-if="showLast"
          :rounded
          :size
          :pagination-selected
          :pagination-unselected
          v-bind="_paginationLast"
        >
          <slot
            name="last"
          />
        </PaginationLast>
      </slot>
    </PaginationList>
  </PaginationRoot>
</template>
<script setup lang="ts">
import type { NPaginationEllipsisProps } from '../../../types'
import { PaginationEllipsis, useForwardProps } from 'radix-vue'
import { computed } from 'vue'
import { cn } from '../../../utils'
import Icon from '../../elements/Icon.vue'

const props = withDefaults(defineProps<NPaginationEllipsisProps>(), {
  paginationEllipsis: '~',
})

const delegatedProps = computed(() => {
  const { class: _, ...delegated } = props

  return delegated
})

const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
  <PaginationEllipsis
    v-bind="forwardedProps"
    :pagination-ellipsis
    :class="cn(
      'pagination-ellipsis-base',
      props.una?.paginationEllipsis,
      props.class,
    )"
  >
    <slot>
      <Icon
        :class="cn(
          'pagination-ellipsis-icon-base',
          props.una?.paginationEllipsisIconBase,
        )"
        :name="forwardedProps?.una?.paginationEllipsisIcon || 'pagination-ellipsis-icon'"
      />
    </slot>
  </PaginationEllipsis>
</template>
<script setup lang="ts">
import type { NPaginationListItemProps } from '../../../types'
import { PaginationListItem, useForwardProps } from 'radix-vue'
import { computed } from 'vue'
import { cn } from '../../../utils'
import Button from '../../elements/Button.vue'

const props = withDefaults(defineProps<NPaginationListItemProps>(), {
  paginationSelected: '~',
  paginationUnselected: '~',
  square: true,
})

const delegatedProps = computed(() => {
  const { value: __, class: _, ...delegated } = props

  return delegated
})

const label = computed(() => {
  return props.label || props.value.toString()
})

const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
  <PaginationListItem
    :value
    as-child
    :data-selected="value === page"
  >
    <Button
      v-bind="forwardedProps"
      :label
      :class="cn(
        'pagination-list-item',
        props.class,
      )"
    >
      <template v-for="(_, name) in $slots" #[name]="slotData">
        <slot :name="name" v-bind="slotData" />
      </template>
    </Button>
  </PaginationListItem>
</template>
<script setup lang="ts">
import type { NPaginationFirstProps } from '../../../types'
import { PaginationFirst, useForwardProps } from 'radix-vue'
import { computed } from 'vue'
import { cn } from '../../../utils'
import Button from '../../elements/Button.vue'

const props = withDefaults(defineProps<NPaginationFirstProps>(), {
  square: true,
  paginationUnselected: '~',
  icon: true,
  label: 'pagination-first-icon',
})

const delegatedProps = computed(() => {
  const { class: _, ...delegated } = props

  return delegated
})

const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
  <PaginationFirst
    as-child
  >
    <slot>
      <Button
        :data-selected="false"
        v-bind="forwardedProps"
        :class="cn(
          'pagination-first',
          props.class,
        )"
      />
    </slot>
  </PaginationFirst>
</template>
<script setup lang="ts">
import type { NPaginationLastProps } from '../../../types'
import { PaginationLast, useForwardProps } from 'radix-vue'
import { computed } from 'vue'
import { cn } from '../../../utils'
import Button from '../../elements/Button.vue'

const props = withDefaults(defineProps<NPaginationLastProps>(), {
  paginationUnselected: '~',
  icon: true,
  square: true,
  label: 'pagination-last-icon',
})

const delegatedProps = computed(() => {
  const { class: _, ...delegated } = props

  return delegated
})

const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
  <PaginationLast as-child>
    <slot>
      <Button
        :data-selected="false"
        v-bind="forwardedProps"
        :class="cn(
          'pagination-last',
          props.class,
        )"
      />
    </slot>
  </paginationlast>
</template>
<script setup lang="ts">
import type { NPaginationNextProps } from '../../../types'
import { PaginationNext, useForwardProps } from 'radix-vue'
import { computed } from 'vue'
import { cn } from '../../../utils'
import Button from '../../elements/Button.vue'

const props = withDefaults(defineProps<NPaginationNextProps>(), {
  paginationUnselected: '~',
  square: true,
  icon: true,
  label: 'pagination-next-icon',
})

const delegatedProps = computed(() => {
  const { class: _, ...delegated } = props

  return delegated
})

const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
  <PaginationNext as-child>
    <slot>
      <Button
        :data-selected="false"
        v-bind="forwardedProps"
        :class="cn(
          'pagination-next',
          props.class)"
      />
    </slot>
  </PaginationNext>
</template>
<script setup lang="ts">
import type { NPaginationPrevProps } from '../../../types'
import { PaginationPrev, useForwardProps } from 'radix-vue'
import { computed } from 'vue'
import { cn } from '../../../utils'
import Button from '../../elements/Button.vue'

const props = withDefaults(defineProps<NPaginationPrevProps>(), {
  paginationUnselected: '~',
  square: true,
  icon: true,
  label: 'pagination-prev-icon',
})

const delegatedProps = computed(() => {
  const { class: _, ...delegated } = props

  return delegated
})

const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
  <PaginationPrev as-child>
    <slot>
      <Button
        :data-selected="false"
        v-bind="forwardedProps"
        :class="cn(
          'pagination-prev',
          props.class,
        )"
      />
    </slot>
  </PaginationPrev>
</template>