Skip to content

Pagination

WxPagination moves between pages. Hand it a Laravel paginator and it needs nothing else.

Driven by a paginator, with a page-size control
Few pages, no ellipsis to hide behind
Small
Without the count
Disabled

Usage

vue
<script setup lang="ts">
import { ref, watch } from 'vue'
import type { Paginated } from '@webx-ui/core'

const orders = ref<Paginated<Order> | null>(null)
const page = ref(1)

watch(
  page,
  async (value) => {
    orders.value = await fetch(`/api/orders?page=${value}`).then((r) => r.json())
  },
  { immediate: true },
)
</script>

<template>
  <wx-pagination v-model:page="page" :paginator="orders" />
</template>

The current page, the page size and the totals all come out of the paginator. v-model:page is there for state the caller would rather own — a query parameter, a store — and takes precedence when it is bound.

Without a paginator, total and per-page are enough:

vue
<wx-pagination v-model:page="page" :total="42" :per-page="10" />

The count

1–30 of 128 is on by default and has nothing to do with the page-size control — a pagination with neither a paginator nor per-page-options still shows where the reader is. It comes from from, to and total, used as the backend sent them, and reads "Nothing to show" when the result is empty.

Turn it off with :show-total="false", or reword it through the slot:

vue
<wx-pagination v-model:page="page" :paginator="orders">
  <template #total="{ from, to, total }"> Заказы {{ from }}–{{ to }} из {{ total }} </template>
</wx-pagination>

Page size

vue
<wx-pagination
  v-model:page="page"
  v-model:per-page="perPage"
  :paginator="orders"
  :per-page-options="[15, 30, 50]"
/>

Changing the size returns to the first page. A larger page can put the current position past the end, and asking the backend for a page that is not there is a worse answer than starting over. The count follows the new size, so picking 30 turns 1–15 of 128 into 1–30 of 128.

This control is the part that is left out unless per-page-options is given.

Props

PropTypeDefaultDescription
paginatorPaginated | nullnullA page as ->paginate() sends it
totalnumberRows in total, without a paginator
lastPagenumberWorked out from total when missing
siblingsnumber1Page buttons either side of the current
perPageOptionsnumber[][]Offers the page-size control
showTotalbooleantrueShows the "31–45 of 128" line
disabledbooleanfalseBlocks every control
size'sm' | 'md' | 'lg''md'Button height
ariaLabelstring'Pagination'Names the nav landmark

Models: v-model:page (number), v-model:per-page (number).

Events: change ({ page, perPage }) — fires once per move, after both models settle.

Slots: total, with from, to and total, for wording the count differently.

It brings its own list styles

The row of pages is a <ul>, and a list is something host stylesheets like to have opinions about. This documentation site puts 8px above every list item after the first — which lands between the buttons and steps them down the row, one after another. So the component states the margins, padding and list style of the row and its items outright, the way Table does for cells and rows.

What gets folded away

The first and last pages are always reachable, the current page keeps siblings neighbours, and what is skipped becomes an ellipsis — except a gap of exactly one page, which is spelled out. An ellipsis standing in for a single number costs a click and saves nothing.

Released under the MIT License.