Pagination

Page navigation with composable page links, previous/next buttons, and ellipsis indicators

Usage

<script setup lang="ts">
import { Pagination } from '@hareui/vue'
</script>
<script setup lang="ts">import { Pagination } from '@hareui/vue'import { ref } from 'vue'const page = ref(1)</script><template>  <Pagination v-model:page="page" class="justify-center" :total="3" /></template>

Anatomy

HeroUI composes Pagination.Summary, Pagination.Content, Pagination.Item, Pagination.Link, Pagination.Previous / Pagination.Next (with PreviousIcon / NextIcon) and Pagination.Ellipsis by hand. HareUI renders the same markup from total and v-model:page:

<template>
  <Pagination v-model:page="page" :total="10" ellipsis>
    <template #summary="{ page, total }">Showing 1-10 of 100 results</template>
    <template #link="{ page, active }" />  <!-- replaces a page number -->
    <template #prevIcon />                 <!-- Pagination.PreviousIcon children -->
    <template #nextIcon />                 <!-- Pagination.NextIcon children -->
  </Pagination>
</template>
  • ellipsis collapses distant pages (first, last, current ± 1), the pattern HeroUI's demos build; pages takes any exact list of numbers and 'ellipsis' entries.
  • prev-label / next-label set the button text (empty = icon only); prev-disabled / next-disabled override the first/last-page defaults.
ui keydata-slotElement
basepagination<nav> root
summarypagination-summaryinfo text (only with #summary)
contentpagination-content<ul> of items
itempagination-item<li> per entry
linkpagination-link, pagination-previous, pagination-nextpage and previous/next buttons
navpagination-previous, pagination-nextextra classes for previous/next (HeroUI's pagination__link--nav)
iconpagination-previous-icon, pagination-next-iconicon wrappers
ellipsispagination-ellipsisellipsis indicator

Examples

Sizes

sm
md
lg
<script setup lang="ts">import { Pagination } from '@hareui/vue'</script><template>  <div class="flex flex-col gap-6">    <div v-for="size in (['sm', 'md', 'lg'] as const)" :key="size" class="flex flex-col gap-2">      <span class="text-xs font-medium text-muted capitalize">{{ size }}</span>      <Pagination class="justify-center" :size="size" :total="3" />    </div>  </div></template>

Disabled

<script setup lang="ts">import { Pagination } from '@hareui/vue'</script><template>  <Pagination class="justify-center" next-disabled prev-disabled :total="3" /></template>

Simple (Previous / Next)

<script setup lang="ts">import { Pagination } from '@hareui/vue'import { computed, ref } from 'vue'const page = ref(1)const totalPages = 10const itemsPerPage = 5const totalItems = 50const startItem = computed(() => (page.value - 1) * itemsPerPage + 1)const endItem = computed(() => Math.min(page.value * itemsPerPage, totalItems))</script><template>  <Pagination v-model:page="page" class="w-full" hide-pages prev-label="Prev" :total="totalPages">    <template #summary>      {{ startItem }} to {{ endItem }} of {{ totalItems }} invoices    </template>  </Pagination></template>

Controlled

<script setup lang="ts">import { Pagination } from '@hareui/vue'import { computed, ref } from 'vue'const page = ref(1)const totalPages = 12const itemsPerPage = 10const totalItems = 120const startItem = computed(() => (page.value - 1) * itemsPerPage + 1)const endItem = computed(() => Math.min(page.value * itemsPerPage, totalItems))</script><template>  <Pagination v-model:page="page" ellipsis :total="totalPages">    <template #summary>      Showing {{ startItem }}-{{ endItem }} of {{ totalItems }} results    </template>  </Pagination></template>

With Ellipsis

<script setup lang="ts">import { Pagination } from '@hareui/vue'import { ref } from 'vue'const page = ref(1)</script><template>  <div class="w-full max-w-2xs overflow-x-auto sm:max-w-full">    <Pagination v-model:page="page" class="justify-center" ellipsis :total="12" />  </div></template>

With Summary

<script setup lang="ts">import { Pagination } from '@hareui/vue'import { computed, ref } from 'vue'const page = ref(1)const totalPages = 12const itemsPerPage = 10const totalItems = 120const startItem = computed(() => (page.value - 1) * itemsPerPage + 1)const endItem = computed(() => Math.min(page.value * itemsPerPage, totalItems))</script><template>  <Pagination v-model:page="page" class="w-full" ellipsis :total="totalPages">    <template #summary>      Showing {{ startItem }}-{{ endItem }} of {{ totalItems }} results    </template>  </Pagination></template>

Custom Icons

You can replace the default chevron icons with the #prevIcon and #nextIcon slots.

<script setup lang="ts">import { Icon } from '@iconify/vue'import { Pagination } from '@hareui/vue'</script><template>  <Pagination class="justify-center" next-label="Forward" prev-label="Back" :total="3">    <template #prevIcon>      <Icon icon="gravity-ui:arrow-left" />    </template>    <template #nextIcon>      <Icon icon="gravity-ui:arrow-right" />    </template>  </Pagination></template>

Customization

Tailwind CSS

<script setup lang="ts">import { Pagination } from '@hareui/vue'// HeroUI swaps `linkClass` for `activeClass` on the current page; here both live on the `link`// key, with the active styles behind `data-[active=true]:`.const linkClass = 'text-muted hover:bg-surface hover:text-foreground'const activeClass = 'data-[active=true]:bg-accent data-[active=true]:text-accent-foreground data-[active=true]:hover:bg-accent-hover'</script><template>  <Pagination    class="justify-center"    :default-page="2"    next-label=""    prev-label=""    :total="3"    :ui="{ content: 'gap-1 rounded-xl bg-default p-1', link: `${linkClass} ${activeClass}` }"  /></template>

Global Configuration

To customize the Pagination classes for the whole app, extend the pagination theme in createHareUI:

app.use(createHareUI({
  ui: {
    pagination: {
      slots: {
        base: 'gap-8',
        link: 'rounded-md',
        summary: 'text-xs font-semibold',
      },
    },
  },
}))

Styling Reference

Slots

  • base → [data-slot="pagination"] – root navigation container with flex layout
  • summary → [data-slot="pagination-summary"] – left-side info text container
  • content → [data-slot="pagination-content"] – container for pagination items
  • item → [data-slot="pagination-item"] – individual item wrapper
  • link → [data-slot="pagination-link"] – page number button (ghost button style)
  • nav → [data-slot="pagination-previous"], [data-slot="pagination-next"] – navigation button modifier
  • ellipsis → [data-slot="pagination-ellipsis"] – ellipsis indicator

Variants

  • size="sm" – small size variant
  • size="md" – medium size variant (default)
  • size="lg" – large size variant

Interactive States

The component supports both CSS pseudo-classes and data attributes for flexibility:

  • Active page: [data-active="true"] or [aria-current="page"]
  • Hover: :hover
  • Focus: :focus-visible
  • Disabled: :disabled or [data-disabled="true"]
  • Pressed: :active

API Reference

Props

PropTypeDefaultDescription
pagenumberundefinedThe current page (1-based). Use v-model:page to control it.
defaultPagenumber1The page shown on first render (uncontrolled).
totalnumber-The total number of pages.
ellipsisbooleanfalseCollapse distant pages into ellipses: the first and last page, the current page and its neighbours (HeroUI's demo pattern). Ignored when pages is set.
pagesPaginationEntry[]-The exact page entries to render, overriding the built-in list.
hidePagesbooleanfalseRender only the previous / next buttons, without page links.
prevLabelstring"Previous"The previous button's label. An empty string renders only the icon.
nextLabelstring"Next"The next button's label. An empty string renders only the icon.
prevDisabledbooleanundefinedDisable the previous button. Defaults to being on the first page.
nextDisabledbooleanundefinedDisable the next button. Defaults to being on the last page.
disabledbooleanfalseDisable every button.
size"md" | "sm" | "lg"'md'The size of the pagination items.
uiComponentSlots<{ slots: { base: string; summary: string; content: string; item: string; link: string[]; nav: string; icon: string; ellipsis: string; }; variants: { size: { sm: { summary: string; link: string; nav: string; ellipsis: string; }; md: {}; lg: { summary: string; link: string; nav: string; ellipsis: string; }; }; }; defaultVariants: { size: string; }; }>-Per-slot class overrides. link applies to page links and previous / next buttons.

Slots

SlotPropsDescription
summary{ page: number; total: number; }Info shown before the links (HeroUI's Pagination.Summary).
link{ page: number; active: boolean; }Replaces a page link's number.
prevIconanyReplaces the previous icon (HeroUI's Pagination.PreviousIcon children).
nextIconanyReplaces the next icon (HeroUI's Pagination.NextIcon children).

Emits

EventPayloadDescription
update:page[page: number]-

Accessibility

  • Semantic <nav> element with aria-label="pagination" and role="navigation"
  • Active page indicated via aria-current="page" on the current link
  • Keyboard navigation via Tab key through all interactive elements
  • Focus ring on keyboard navigation via :focus-visible
  • Ellipsis marked with aria-hidden="true" to avoid screen reader confusion
  • Icon-only previous/next buttons get an aria-label
  • Disabled buttons use the native disabled attribute

On this page

No Headings