Menu

Change language

GitHub repo

Pagination

paginationui

Page-number bar with previous/next, ellipsis collapse, and a configurable visible-window size. Pair with a data-table or any paged list.

Installation

$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/pagination.json
Manifest:https://uipkge.dev/r/svelte/pagination.jsonInstalls to:src/lib/components/ui/pagination/

Examples

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
pagenumber—required
pageCountnumber—required

npm dependencies

Files installed (9)

  • src/lib/components/ui/pagination/Pagination.svelte4.1 kB
    <script lang="ts" module>
      import type { Snippet } from 'svelte'
      import type { HTMLAttributes } from 'svelte/elements'
    
      export interface PaginationItem {
        type: 'page' | 'ellipsis'
        value?: number
      }
    
      export interface PaginationSlotProps {
        page: number
        pageCount: number
      }
    
      export interface PaginationProps extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
        /** Controlled page (1-based). Pair with `bind:page` or `onPageChange`. */
        page?: number | undefined
        defaultPage?: number
        /** Total row count across all pages. */
        total?: number
        itemsPerPage?: number
        siblingCount?: number
        showEdges?: boolean
        disabled?: boolean
        ariaLabel?: string
        children?: Snippet<[PaginationSlotProps]>
        onPageChange?: (page: number) => void
        ref?: HTMLElement | null
      }
    
      export interface PaginationContext {
        readonly page: number
        readonly pageCount: number
        readonly disabled: boolean
        readonly items: PaginationItem[]
        goTo: (value: number) => void
        goFirst: () => void
        goPrev: () => void
        goNext: () => void
        goLast: () => void
      }
    
      export const PAGINATION_CTX = Symbol('uipkge-pagination')
    
      /** Page/ellipsis window around the current page. Edges always win; a gap of exactly one page renders the page, not dots. */
      export function buildPaginationItems(
        page: number,
        pageCount: number,
        siblingCount = 1,
        showEdges = false,
      ): PaginationItem[] {
        const total = Math.max(1, pageCount)
        const current = Math.min(Math.max(page, 1), total)
        const siblings = Math.max(0, siblingCount)
        const start = Math.max(1, current - siblings)
        const end = Math.min(total, current + siblings)
        const items: PaginationItem[] = []
        if (showEdges) {
          if (start > 1) {
            items.push({ type: 'page', value: 1 })
            if (start > 2) items.push({ type: 'ellipsis' })
          }
          for (let v = start; v <= end; v++) items.push({ type: 'page', value: v })
          if (end < total) {
            if (end < total - 1) items.push({ type: 'ellipsis' })
            items.push({ type: 'page', value: total })
          }
          return items
        }
        if (start > 1) items.push({ type: 'ellipsis' })
        for (let v = start; v <= end; v++) items.push({ type: 'page', value: v })
        if (end < total) items.push({ type: 'ellipsis' })
        return items
      }
    </script>
    
    <script lang="ts">
      import { setContext } from 'svelte'
      import { cn } from '$lib/utils'
    
      let {
        class: className,
        page = $bindable(undefined),
        defaultPage = 1,
        total = 0,
        itemsPerPage = 10,
        siblingCount = 1,
        showEdges = false,
        disabled = false,
        ariaLabel = 'Pagination',
        children,
        onPageChange,
        ref = $bindable(null),
        ...restProps
      }: PaginationProps = $props()
    
      // svelte-ignore state_referenced_locally -- uncontrolled seed: later defaultPage changes must not reset the page.
      let internalPage = $state(defaultPage)
      const pageCount = $derived(Math.max(1, Math.ceil(total / Math.max(1, itemsPerPage))))
      const currentPage = $derived(Math.min(Math.max(page ?? internalPage, 1), pageCount))
      const items = $derived(buildPaginationItems(currentPage, pageCount, siblingCount, showEdges))
    
      function setPage(value: number) {
        if (disabled) return
        const next = Math.min(Math.max(value, 1), pageCount)
        if (page === undefined) internalPage = next
        page = next
        onPageChange?.(next)
      }
    
      // Getters stay reactive: consumers read them inside $derived / templates.
      setContext<PaginationContext>(PAGINATION_CTX, {
        get page() {
          return currentPage
        },
        get pageCount() {
          return pageCount
        },
        get disabled() {
          return disabled
        },
        get items() {
          return items
        },
        goTo: setPage,
        goFirst: () => setPage(1),
        goPrev: () => setPage(currentPage - 1),
        goNext: () => setPage(currentPage + 1),
        goLast: () => setPage(pageCount),
      })
    </script>
    
    <nav
      bind:this={ref}
      aria-label={ariaLabel}
      data-uipkge
      data-slot="pagination"
      class={cn('flex items-center gap-1', className)}
      {...restProps}
    >
      {@render children?.({ page: currentPage, pageCount })}
    </nav>
    
  • src/lib/components/ui/pagination/PaginationEllipsis.svelte0.7 kB
  • src/lib/components/ui/pagination/PaginationFirst.svelte1.1 kB
  • src/lib/components/ui/pagination/PaginationLast.svelte1.1 kB
  • src/lib/components/ui/pagination/PaginationList.svelte1.1 kB
  • src/lib/components/ui/pagination/PaginationListItem.svelte1.3 kB
  • src/lib/components/ui/pagination/PaginationNext.svelte1.1 kB
  • src/lib/components/ui/pagination/PaginationPrev.svelte1.1 kB
  • src/lib/components/ui/pagination/index.ts0.9 kB

Raw manifest:https://uipkge.dev/r/svelte/pagination.json