Pagination
<nav aria-label="Pagination">
Splits a large data set into pages. Shows the current page, allows jumping to a specific page, and provides previous/next navigation.
Overview
Preview
Showing 21–30 of 94 results
Numbered pages
Preview
html
<nav aria-label="Pagination" class="flex items-center gap-1">
<!-- Prev -->
<button
:disabled="page === 1"
class="flex h-9 w-9 items-center justify-center rounded-md border border-zinc-800 text-zinc-500 hover:border-zinc-700 hover:text-zinc-300 transition-colors disabled:opacity-30 disabled:pointer-events-none"
>
<svg class="h-4 w-4" ...>‹</svg>
</button>
<!-- Page numbers (ellipsis handled by computed) -->
<template v-for="p in pages" :key="p">
<span v-if="p === '...'" class="flex h-9 w-9 items-center justify-center text-xs text-zinc-600">…</span>
<button
v-else
@click="page = p"
class="flex h-9 w-9 items-center justify-center rounded-md text-sm font-medium transition-all"
:class="page === p
? 'bg-[#D40C37] text-white shadow-sm'
: 'border border-zinc-800 text-zinc-400 hover:border-zinc-700 hover:text-zinc-200'"
>
{{ p }}
</button>
</template>
<!-- Next -->
<button
:disabled="page === total"
class="flex h-9 w-9 items-center justify-center rounded-md border border-zinc-800 text-zinc-500 hover:border-zinc-700 hover:text-zinc-300 transition-colors disabled:opacity-30 disabled:pointer-events-none"
>
<svg class="h-4 w-4" ...>›</svg>
</button>
</nav>Ellipsis logic (Vue computed)
vue
<script setup>
import { ref, computed } from 'vue'
const page = ref(1)
const total = ref(12)
const pages = computed(() => {
const p = page.value
const t = total.value
if (t <= 7) return Array.from({ length: t }, (_, i) => i + 1)
if (p <= 3) return [1, 2, 3, 4, '...', t]
if (p >= t - 2) return [1, '...', t - 3, t - 2, t - 1, t]
return [1, '...', p - 1, p, p + 1, '...', t]
})
</script>With result count
Preview
Showing 11–20 of 94 results
html
<div class="flex flex-wrap items-center justify-between gap-3">
<p class="text-xs text-zinc-500 tabular-nums">
Showing <span class="text-zinc-300">{{ from }}–{{ to }}</span> of <span class="text-zinc-300">{{ total }}</span> results
</p>
<nav class="flex items-center gap-1">
<button :disabled="page === 1" class="flex h-8 items-center gap-1.5 rounded-md border border-zinc-800 px-2.5 text-xs font-medium text-zinc-500 hover:border-zinc-700 hover:text-zinc-300 disabled:opacity-30 transition-colors">
‹ Prev
</button>
<button :disabled="page === lastPage" class="flex h-8 items-center gap-1.5 rounded-md border border-zinc-800 px-2.5 text-xs font-medium text-zinc-500 hover:border-zinc-700 hover:text-zinc-300 disabled:opacity-30 transition-colors">
Next ›
</button>
</nav>
</div>Compact (prev/next only)
html
<nav class="flex items-center gap-2" aria-label="Pagination">
<button @click="page--" :disabled="page === 1"
class="rounded-md border border-zinc-700 px-3 py-1.5 text-xs font-medium text-zinc-400 hover:bg-zinc-800 disabled:opacity-30 transition-colors">
← Previous
</button>
<span class="text-xs text-zinc-600 tabular-nums">Page {{ page }} of {{ total }}</span>
<button @click="page++" :disabled="page === total"
class="rounded-md border border-zinc-700 px-3 py-1.5 text-xs font-medium text-zinc-400 hover:bg-zinc-800 disabled:opacity-30 transition-colors">
Next →
</button>
</nav>Accessibility notes
- Wrap in
<nav aria-label="Pagination">to create a landmark - The active page button should have
aria-current="page" - Disabled prev/next buttons should use the
disabledattribute (not just styling) - Ellipsis spans should have
aria-hidden="true"since they're decorative