---
url: "https://vueda.dev/v3/reference/components/pagination.md"
title: "Pagination"
---

<script setup>
import Pagination from "@vueda/navigation/pagination/Pagination.vue";
import PaginationBar from "@vueda/navigation/pagination/PaginationBar.vue";
import PaginationContent from "@vueda/navigation/pagination/PaginationContent.vue";
import PaginationEllipsis from "@vueda/navigation/pagination/PaginationEllipsis.vue";
import PaginationFirst from "@vueda/navigation/pagination/PaginationFirst.vue";
import PaginationItem from "@vueda/navigation/pagination/PaginationItem.vue";
import PaginationLast from "@vueda/navigation/pagination/PaginationLast.vue";
import PaginationMeta from "@vueda/navigation/pagination/PaginationMeta.vue";
import PaginationNext from "@vueda/navigation/pagination/PaginationNext.vue";
import PaginationPrevious from "@vueda/navigation/pagination/PaginationPrevious.vue";
import PaginationFooter from "@vueda/navigation/pagination/PaginationFooter.vue";
import NativeSelect from "@vueda/controls/native-select/NativeSelect.vue";
import NativeSelectOption from "@vueda/controls/native-select/NativeSelectOption.vue";
import { ref } from "vue";

const barPage = ref(1);
const barPerPage = ref("25");
const widgetPage = ref(3);
const widgetPerPage = ref(25);
</script>

# Pagination

This page shows the [Visual Contract](/v3/reference/glossary.md#visual-contract) of the pagination controls: page
primitives, the footer bar and its label, and the footer that list views
render. [Components](/v3/reference/components/index.md) describes the rules every component page shares.
Breadcrumb, NavigationMenu, and Menubar are on [Navigation](/v3/reference/components/navigation.md).

## Primitives

[Pagination](/v3/reference/api/vue/Pagination.md) renders a `<nav>` landmark, and
[PaginationContent](/v3/reference/api/vue/PaginationContent.md) lays its controls out in one row. A
re-skin keeps these parts distinct:

- **Page items:** [PaginationItem](/v3/reference/api/vue/PaginationItem.md) renders one numbered page
  as a button. Inactive pages gain a fill and edge on hover. The current
  page keeps a foreground-colored edge at rest, so it stands apart from the
  navigation buttons. Its `size` prop takes `sm`, `default`, `lg`, `icon-sm`,
  `icon`, or `icon-lg`, the size tiers of [Button](/v3/reference/api/vue/Button.md), and
  defaults to `icon`.
- **Navigation buttons:** [PaginationFirst](/v3/reference/api/vue/PaginationFirst.md),
  [PaginationPrevious](/v3/reference/api/vue/PaginationPrevious.md), [PaginationNext](/v3/reference/api/vue/PaginationNext.md),
  and [PaginationLast](/v3/reference/api/vue/PaginationLast.md) are icon-only outline squares on the
  compact control tier. First and Last use double-angle glyphs; Previous and
  Next use single chevrons. Each carries a screen-reader label.
- **Ellipsis:** [PaginationEllipsis](/v3/reference/api/vue/PaginationEllipsis.md) marks omitted pages and
  carries a "More pages" screen-reader label.
- **Disabled:** a disabled control takes no pointer input. A disabled outline
  control keeps only its glyph or number.

Theme keys: [Pagination](/v3/reference/theming/keys/Pagination.md), [PaginationContent](/v3/reference/theming/keys/PaginationContent.md),
[PaginationItem](/v3/reference/theming/keys/PaginationItem.md), [NavigationPaginationNavButton](/v3/reference/theming/keys/NavigationPaginationNavButton.md),
[PaginationEllipsis](/v3/reference/theming/keys/PaginationEllipsis.md). Control heights:
[--vueda-control-height](/v3/reference/theming/tokens/control-sizing-vueda-canon.md#css-token-vueda-control-height) and
[--vueda-control-height-sm](/v3/reference/theming/tokens/control-sizing-vueda-canon.md#css-token-vueda-control-height-sm).

<VuedaDemo class="grid gap-6">
  <DemoCard title="default" description="(sibling-count=1, page 5 of 10)">
    <Pagination v-slot="{ page }" :total="100" :items-per-page="10" :sibling-count="1" :default-page="5">
      <PaginationContent v-slot="{ items }">
        <PaginationPrevious />
        <template v-for="(item, idx) in items" :key="idx">
          <PaginationItem v-if="item.type === 'page'" :value="item.value" :is-active="item.value === page">
            {{ item.value }}
          </PaginationItem>
          <PaginationEllipsis v-else-if="item.type === 'ellipsis'" />
        </template>
        <PaginationNext />
      </PaginationContent>
    </Pagination>
  </DemoCard>
  <DemoCard title="show-edges" description="(first/last buttons + edge page numbers)">
    <Pagination v-slot="{ page }" :total="100" :items-per-page="10" :sibling-count="1" :default-page="5" :show-edges="true">
      <PaginationContent v-slot="{ items }">
        <PaginationFirst />
        <PaginationPrevious />
        <template v-for="(item, idx) in items" :key="idx">
          <PaginationItem v-if="item.type === 'page'" :value="item.value" :is-active="item.value === page">
            {{ item.value }}
          </PaginationItem>
          <PaginationEllipsis v-else-if="item.type === 'ellipsis'" />
        </template>
        <PaginationNext />
        <PaginationLast />
      </PaginationContent>
    </Pagination>
  </DemoCard>
  <DemoCard title="at the first page" description="(show-edges, page 1 of 10)">
    <Pagination v-slot="{ page }" :total="100" :items-per-page="10" :sibling-count="1" :default-page="1" :show-edges="true">
      <PaginationContent v-slot="{ items }">
        <PaginationFirst />
        <PaginationPrevious />
        <template v-for="(item, idx) in items" :key="idx">
          <PaginationItem v-if="item.type === 'page'" :value="item.value" :is-active="item.value === page">
            {{ item.value }}
          </PaginationItem>
          <PaginationEllipsis v-else-if="item.type === 'ellipsis'" />
        </template>
        <PaginationNext />
        <PaginationLast />
      </PaginationContent>
    </Pagination>
  </DemoCard>
  <DemoCard title="disabled">
    <Pagination v-slot="{ page }" :total="100" :items-per-page="10" :sibling-count="1" :default-page="5" disabled>
      <PaginationContent v-slot="{ items }">
        <PaginationPrevious />
        <template v-for="(item, idx) in items" :key="idx">
          <PaginationItem v-if="item.type === 'page'" :value="item.value" :is-active="item.value === page">
            {{ item.value }}
          </PaginationItem>
          <PaginationEllipsis v-else-if="item.type === 'ellipsis'" />
        </template>
        <PaginationNext />
      </PaginationContent>
    </Pagination>
  </DemoCard>
</VuedaDemo>

## Footer bar

[PaginationBar](/v3/reference/api/vue/PaginationBar.md) is the footer row that sits against the
bottom of a data surface, such as a grid or table. It draws a [Hairline](/v3/reference/glossary.md#hairline)
top edge, the card surface, and bottom corners that match the card above it. It
spreads its contents to both ends of the row. You compose those contents in its
default slot.

[PaginationMeta](/v3/reference/api/vue/PaginationMeta.md) is the count or page summary label. It
renders mono supporting text in the muted color.

Theme keys: [NavigationPaginationBar](/v3/reference/theming/keys/NavigationPaginationBar.md),
[PaginationMeta](/v3/reference/theming/keys/PaginationMeta.md).

<VuedaDemo class="grid gap-6">
  <DemoCard title="PaginationMeta" description="(mono supporting-text summary)">
    <div class="flex flex-col gap-2">
      <PaginationMeta>142 invoices</PaginationMeta>
      <PaginationMeta>Showing 1 to 25 of 142 · Page 1 of 6</PaginationMeta>
    </div>
  </DemoCard>
  <DemoCard title="PaginationBar" description="(footer substrate seated against a data surface)">
    <div class="rounded-vueda-card hairline hairline-border bg-card overflow-clip">
      <div class="px-3 py-6 text-center text-sm text-muted-foreground">grid / table body</div>
      <PaginationBar>
        <PaginationMeta>Showing 1 to 25 of 142 · Page 1 of 6</PaginationMeta>
        <div class="flex items-center gap-3">
          <span class="flex items-center gap-1.5 text-xs text-muted-foreground">
            Rows
            <NativeSelect v-model="barPerPage" :theme-override="{ NativeSelect: { root: { class: { 'w-full': false } } } }">
              <NativeSelectOption value="10">10</NativeSelectOption>
              <NativeSelectOption value="25">25</NativeSelectOption>
              <NativeSelectOption value="50">50</NativeSelectOption>
              <NativeSelectOption value="100">100</NativeSelectOption>
            </NativeSelect>
          </span>
          <Pagination v-model:page="barPage" :total="142" :items-per-page="25">
            <PaginationContent>
              <PaginationFirst />
              <PaginationPrevious />
              <PaginationNext />
              <PaginationLast />
            </PaginationContent>
          </Pagination>
        </div>
      </PaginationBar>
    </div>
  </DemoCard>
</VuedaDemo>

## Composed footer

[PaginationFooter](/v3/reference/api/vue/PaginationFooter.md) is the footer that
[ViewList](/v3/reference/api/vue/ViewList.md) and [ViewHistoryList](/v3/reference/api/vue/ViewHistoryList.md) render.
It fills a `PaginationBar` with two groups:

- **Start:** the "Showing X to Y of N" range read-out, with the figures
  emphasized against the muted label, then the rows-per-page selector.
- **End:** the navigation cluster: First, Previous, a mono "Page N of M"
  indicator, Next, and Last.

On a narrow viewport the groups wrap and center, with the read-out and selector
above the navigation cluster.

The **All** entry, last in the default options, loads every page. Choosing it hides the
navigation cluster, and the read-out changes to "All N results".
[List Preferences](/v3/guides/configure-crud-views.md#list-preferences)
describes how a list saves the chosen page size.

While [`loading`](/v3/reference/api/vue/PaginationFooter.md#prop-loading) is `true`,
the read-out is blank and the indicator shows "Page N of ?". The navigation
controls show their disabled state.

To replace the read-out or the selector, fill the
[`meta`](/v3/reference/api/vue/PaginationFooter.md#slot-meta) or
[`rows-per-page`](/v3/reference/api/vue/PaginationFooter.md#slot-rows-per-page) slot.
Setting
[`showTotalRecordNum`](/v3/reference/api/vue/PaginationFooter.md#prop-showTotalRecordNum)
to `false` hides the default read-out.

Theme key: [PaginationFooter](/v3/reference/theming/keys/PaginationFooter.md).

<VuedaDemo class="grid gap-6">
  <DemoCard title="PaginationFooter" description="(total=142, rows=25)">
    <PaginationFooter
      v-model:current-page="widgetPage"
      v-model:per-page="widgetPerPage"
      :total-records="142"
      :rows="25"
    />
  </DemoCard>
</VuedaDemo>
