Pagination
paginationuiPage-number bar with previous/next, ellipsis collapse, and a configurable visible-window size. Pair with a data-table or any paged list.
Installation
$pnpm dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/pagination.json$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/pagination.json$yarn dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/pagination.json$bunx shadcn-svelte@latest add https://uipkge.dev/r/svelte/pagination.jsonManifest:
https://uipkge.dev/r/svelte/pagination.jsonInstalls to:src/lib/components/ui/pagination/Examples
Loading interactive previews…
Loading interactive previews…
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
page | number | — | required |
pageCount | number | — | 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
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface PaginationEllipsisProps extends HTMLAttributes<HTMLSpanElement> { children?: Snippet } </script> <script lang="ts"> import { MoreHorizontal } from '@lucide/svelte' import { cn } from '$lib/utils' let { class: className, children, ...restProps }: PaginationEllipsisProps = $props() </script> <li> <span aria-hidden="true" data-uipkge data-slot="pagination-ellipsis" class={cn('shrink-0', className)} {...restProps}> {#if children} {@render children()} {:else} <MoreHorizontal class="size-4" aria-hidden="true" /> {/if} </span> </li>src/lib/components/ui/pagination/PaginationFirst.svelte1.1 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLButtonAttributes } from 'svelte/elements' export interface PaginationFirstProps extends HTMLButtonAttributes { children?: Snippet ref?: HTMLButtonElement | null } </script> <script lang="ts"> import { getContext } from 'svelte' import { ChevronsLeft } from '@lucide/svelte' import { PAGINATION_CTX, type PaginationContext } from './Pagination.svelte' let { class: className, children, disabled = false, ref = $bindable(null), ...restProps }: PaginationFirstProps = $props() const ctx = getContext<PaginationContext | undefined>(PAGINATION_CTX) if (!ctx) throw new Error('PaginationFirst must be used inside <Pagination>') const isDisabled = $derived(disabled || ctx.disabled || ctx.page <= 1) </script> <button bind:this={ref} type="button" data-uipkge data-slot="pagination-first" aria-label="Go to first page" disabled={isDisabled} class={className} {...restProps} onclick={() => ctx.goFirst()} > {#if children} {@render children()} {:else} <ChevronsLeft class="size-4" aria-hidden="true" /> {/if} </button>src/lib/components/ui/pagination/PaginationLast.svelte1.1 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLButtonAttributes } from 'svelte/elements' export interface PaginationLastProps extends HTMLButtonAttributes { children?: Snippet ref?: HTMLButtonElement | null } </script> <script lang="ts"> import { getContext } from 'svelte' import { ChevronsRight } from '@lucide/svelte' import { PAGINATION_CTX, type PaginationContext } from './Pagination.svelte' let { class: className, children, disabled = false, ref = $bindable(null), ...restProps }: PaginationLastProps = $props() const ctx = getContext<PaginationContext | undefined>(PAGINATION_CTX) if (!ctx) throw new Error('PaginationLast must be used inside <Pagination>') const isDisabled = $derived(disabled || ctx.disabled || ctx.page >= ctx.pageCount) </script> <button bind:this={ref} type="button" data-uipkge data-slot="pagination-last" aria-label="Go to last page" disabled={isDisabled} class={className} {...restProps} onclick={() => ctx.goLast()} > {#if children} {@render children()} {:else} <ChevronsRight class="size-4" aria-hidden="true" /> {/if} </button>src/lib/components/ui/pagination/PaginationList.svelte1.1 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' import type { PaginationItem } from './Pagination.svelte' export interface PaginationListSlotProps { items: PaginationItem[] } export interface PaginationListProps extends Omit<HTMLAttributes<HTMLUListElement>, 'children'> { children?: Snippet<[PaginationListSlotProps]> ref?: HTMLUListElement | null } </script> <script lang="ts"> import { getContext } from 'svelte' import { cn } from '$lib/utils' import { PAGINATION_CTX, type PaginationContext } from './Pagination.svelte' let { class: className, children, ref = $bindable(null), ...restProps }: PaginationListProps = $props() const ctx = getContext<PaginationContext | undefined>(PAGINATION_CTX) if (!ctx) throw new Error('PaginationList must be used inside <Pagination>') </script> <ul bind:this={ref} data-uipkge data-slot="pagination-list" class={cn('flex items-center gap-1', className)} {...restProps} > {@render children?.({ items: ctx.items })} </ul>src/lib/components/ui/pagination/PaginationListItem.svelte1.3 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLButtonAttributes } from 'svelte/elements' export interface PaginationListItemProps extends HTMLButtonAttributes { /** 1-based page number this item navigates to. */ value: number children?: Snippet ref?: HTMLButtonElement | null } </script> <script lang="ts"> import { getContext } from 'svelte' import { cn } from '$lib/utils' import { PAGINATION_CTX, type PaginationContext } from './Pagination.svelte' let { class: className, value, children, disabled = false, ref = $bindable(null), ...restProps }: PaginationListItemProps = $props() const ctx = getContext<PaginationContext | undefined>(PAGINATION_CTX) if (!ctx) throw new Error('PaginationListItem must be used inside <Pagination>') const isActive = $derived(ctx.page === value) const isDisabled = $derived(disabled || ctx.disabled) </script> <li> <button bind:this={ref} type="button" data-uipkge data-slot="pagination-list-item" data-selected={isActive ? true : undefined} aria-current={isActive ? 'page' : undefined} aria-label={`Page ${value}`} disabled={isDisabled} class={cn('shrink-0', className)} {...restProps} onclick={() => ctx.goTo(value)} > {@render children?.()} </button> </li>src/lib/components/ui/pagination/PaginationNext.svelte1.1 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLButtonAttributes } from 'svelte/elements' export interface PaginationNextProps extends HTMLButtonAttributes { children?: Snippet ref?: HTMLButtonElement | null } </script> <script lang="ts"> import { getContext } from 'svelte' import { ChevronRight } from '@lucide/svelte' import { PAGINATION_CTX, type PaginationContext } from './Pagination.svelte' let { class: className, children, disabled = false, ref = $bindable(null), ...restProps }: PaginationNextProps = $props() const ctx = getContext<PaginationContext | undefined>(PAGINATION_CTX) if (!ctx) throw new Error('PaginationNext must be used inside <Pagination>') const isDisabled = $derived(disabled || ctx.disabled || ctx.page >= ctx.pageCount) </script> <button bind:this={ref} type="button" data-uipkge data-slot="pagination-next" aria-label="Go to next page" disabled={isDisabled} class={className} {...restProps} onclick={() => ctx.goNext()} > {#if children} {@render children()} {:else} <ChevronRight class="size-4" aria-hidden="true" /> {/if} </button>src/lib/components/ui/pagination/PaginationPrev.svelte1.1 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLButtonAttributes } from 'svelte/elements' export interface PaginationPrevProps extends HTMLButtonAttributes { children?: Snippet ref?: HTMLButtonElement | null } </script> <script lang="ts"> import { getContext } from 'svelte' import { ChevronLeft } from '@lucide/svelte' import { PAGINATION_CTX, type PaginationContext } from './Pagination.svelte' let { class: className, children, disabled = false, ref = $bindable(null), ...restProps }: PaginationPrevProps = $props() const ctx = getContext<PaginationContext | undefined>(PAGINATION_CTX) if (!ctx) throw new Error('PaginationPrev must be used inside <Pagination>') const isDisabled = $derived(disabled || ctx.disabled || ctx.page <= 1) </script> <button bind:this={ref} type="button" data-uipkge data-slot="pagination-prev" aria-label="Go to previous page" disabled={isDisabled} class={className} {...restProps} onclick={() => ctx.goPrev()} > {#if children} {@render children()} {:else} <ChevronLeft class="size-4" aria-hidden="true" /> {/if} </button>src/lib/components/ui/pagination/index.ts0.9 kB
export { default as Pagination, buildPaginationItems, type PaginationProps, type PaginationSlotProps, type PaginationItem, type PaginationContext, } from './Pagination.svelte' export { default as PaginationList, type PaginationListProps, type PaginationListSlotProps, } from './PaginationList.svelte' export { default as PaginationListItem, type PaginationListItemProps } from './PaginationListItem.svelte' export { default as PaginationFirst, type PaginationFirstProps } from './PaginationFirst.svelte' export { default as PaginationPrev, type PaginationPrevProps } from './PaginationPrev.svelte' export { default as PaginationNext, type PaginationNextProps } from './PaginationNext.svelte' export { default as PaginationLast, type PaginationLastProps } from './PaginationLast.svelte' export { default as PaginationEllipsis, type PaginationEllipsisProps } from './PaginationEllipsis.svelte'
Raw manifest:https://uipkge.dev/r/svelte/pagination.json