🔥 Oxide UI v0.1.0 — Dark mode only, copy-paste ready. Get started →
Skip to content

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 disabled attribute (not just styling)
  • Ellipsis spans should have aria-hidden="true" since they're decorative

Released under the MIT License.