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>ellipsiscollapses distant pages (first, last, current ± 1), the pattern HeroUI's demos build;pagestakes any exact list of numbers and'ellipsis'entries.prev-label/next-labelset the button text (empty = icon only);prev-disabled/next-disabledoverride the first/last-page defaults.
ui key | data-slot | Element |
|---|---|---|
base | pagination | <nav> root |
summary | pagination-summary | info text (only with #summary) |
content | pagination-content | <ul> of items |
item | pagination-item | <li> per entry |
link | pagination-link, pagination-previous, pagination-next | page and previous/next buttons |
nav | pagination-previous, pagination-next | extra classes for previous/next (HeroUI's pagination__link--nav) |
icon | pagination-previous-icon, pagination-next-icon | icon wrappers |
ellipsis | pagination-ellipsis | ellipsis 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 layoutsummary→[data-slot="pagination-summary"]– left-side info text containercontent→[data-slot="pagination-content"]– container for pagination itemsitem→[data-slot="pagination-item"]– individual item wrapperlink→[data-slot="pagination-link"]– page number button (ghost button style)nav→[data-slot="pagination-previous"],[data-slot="pagination-next"]– navigation button modifierellipsis→[data-slot="pagination-ellipsis"]– ellipsis indicator
Variants
size="sm"– small size variantsize="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:
:disabledor[data-disabled="true"] - Pressed:
:active
API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
page | number | undefined | The current page (1-based). Use v-model:page to control it. |
defaultPage | number | 1 | The page shown on first render (uncontrolled). |
total | number | - | The total number of pages. |
ellipsis | boolean | false | Collapse distant pages into ellipses: the first and last page, the current page and its
neighbours (HeroUI's demo pattern). Ignored when pages is set. |
pages | PaginationEntry[] | - | The exact page entries to render, overriding the built-in list. |
hidePages | boolean | false | Render only the previous / next buttons, without page links. |
prevLabel | string | "Previous" | The previous button's label. An empty string renders only the icon. |
nextLabel | string | "Next" | The next button's label. An empty string renders only the icon. |
prevDisabled | boolean | undefined | Disable the previous button. Defaults to being on the first page. |
nextDisabled | boolean | undefined | Disable the next button. Defaults to being on the last page. |
disabled | boolean | false | Disable every button. |
size | "md" | "sm" | "lg" | 'md' | The size of the pagination items. |
ui | ComponentSlots<{ 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
| Slot | Props | Description |
|---|---|---|
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. |
prevIcon | any | Replaces the previous icon (HeroUI's Pagination.PreviousIcon children). |
nextIcon | any | Replaces the next icon (HeroUI's Pagination.NextIcon children). |
Emits
| Event | Payload | Description |
|---|---|---|
update:page | [page: number] | - |
Accessibility
- Semantic
<nav>element witharia-label="pagination"androle="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
disabledattribute



