Vize

Pager

Swipeable, scroll-snap pager with typed page ids and a segmented tab bar (APG tabs).

Package @vizejs/ui/pager
Maturity stable
Own the source vize lib pull pager
Requires context, controllable-state, id
Aliases view pager, swipe tabs, segmented pager, page view, swipeable views
Covers WAI-ARIA Tabs, CSS scroll snap, Android ViewPager2, UIPageViewController

Minimal setup

Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).

vp install @vizejs/ui

Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.

import { Pager, PagerTabList, PagerTab, PagerViewport, PagerPage } from "@vizejs/ui/pager";

Or copy the source into your project with vize lib pull pager (see Source Distribution).

Try it

This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.

Open the interactive example · Styling these components

Copy the example

<!-- Controlled swipeable pager whose segmented tabs and scroll-snap pages stay in sync. -->
<script setup lang="ts">
import { ref } from "vue";

import { Pager, PagerPage, PagerTab, PagerTabList, PagerViewport } from "@vizejs/ui/pager";

type Library = "photos" | "albums" | "shared";

const pages: readonly Library[] = ["photos", "albums", "shared"];
const labels: Record<Library, string> = { photos: "Photos", albums: "Albums", shared: "Shared" };
const active = ref<Library>("photos");
// Swiping needs a horizontal scroll-snap viewport with one full-width page per snap point.
const viewportLayout = { display: "flex", overflowX: "auto", scrollSnapType: "x mandatory" };
const pageLayout = { flex: "0 0 100%", scrollSnapAlign: "start" };
</script>

<template>
  <Pager v-model="active" :pages>
    <PagerTabList aria-label="Library">
      <PagerTab v-for="page in pages" :key="page" :page>{{ labels[page] }}</PagerTab>
    </PagerTabList>
    <PagerViewport :style="viewportLayout">
      <PagerPage v-for="page in pages" :key="page" :page :style="pageLayout">
        <p>{{ labels[page] }} in your library.</p>
      </PagerPage>
    </PagerViewport>
  </Pager>
</template>

API

Pager<PageId extends string>

Source: src/families/navigation/pager/pager.vue

Props

Prop Type Default Description
pages (required) readonly PageId[] required Page ids in order; their literal union types v-model and change.
modelValue PageId undefined Controlled active page. undefined selects uncontrolled behavior.
defaultValue PageId pages[0] Initial uncontrolled page.
id string | null undefined Base id for tabs and panels. null and undefined select a deterministic fallback.

Events

Event Payload Description
update:modelValue [page: PageId] Fired when the active page requests a change.
change [page: PageId, previous: PageId, reason: PagerChangeReason] Fired after the active page changes, with the previous page and what caused it.

Slots

Slot Slot props Description
default PagerSlotState<PageId> Renders PagerTabList and PagerViewport with the typed page state.

Exposed

Member Type Description
pages readonly PageId[] Page ids in order.
active PageId Active page.
index number Zero-based index of the active page.
count number Number of pages.
goTo (page: PageId) => boolean Scroll to a page and make it active.
next () => boolean Go to the next page (no wrap); returns whether it moved.
previous () => boolean Go to the previous page (no wrap); returns whether it moved.

PagerTabList

Source: src/families/navigation/pager/pager-tab-list.vue

Props

Prop Type Default Description
ariaLabel string undefined Accessible name of the tab list.

Slots

Slot Slot props Description
default Record<string, never> PagerTab segments, in page order.

PagerTab

Source: src/families/navigation/pager/pager-tab.vue

Props

Prop Type Default Description
page (required) string required Page this segment selects; must be one of the Pager pages.

Slots

Slot Slot props Description
default { readonly selected: boolean } Segment label with its selection state.

PagerViewport

Source: src/families/navigation/pager/pager-viewport.vue

Props

Prop Type Default Description
settleDelay number 120 Quiet period (ms) after the last scroll event before a user scroll settles, for engines without scrollend.

Slots

Slot Slot props Description
default Record<string, never> PagerPage panels, in page order.

PagerPage

Source: src/families/navigation/pager/pager-page.vue

Props

Prop Type Default Description
page (required) string required Page id this panel renders; must be one of the Pager pages.

Slots

Slot Slot props Description
default { readonly active: boolean } Page contents; stay mounted so swiping shows neighbors.

Behavior

Normative state x input -> outcome table for pager.vue, pager-tab-list.vue, pager-tab.vue, pager-viewport.vue, and pager-page.vue (@vizejs/ui/pager): a swipeable, scroll-snap page view whose segmented tab bar follows the WAI-ARIA APG tabs pattern with automatic activation. pages infers the page-id union. Minimal consumer CSS: the viewport is display: flex; overflow-x: auto; scroll-snap-type: x mandatory and pages are flex: 0 0 100%; scroll-snap-align: start. Every row is proven by the named test in pager.test.ts or pager-ssr.test.ts.

# State Input Outcome Proven by
PG1 seeded render labelled tablist; selected tab is the roving tab stop wired to its tabpanel; off-screen panels are inert renders APG tabs wired to scroll-snap pages with inert off-screen panels
PG2 any tab click the page becomes active, the viewport smooth-scrolls to it, and change(page, previous, "tab") fires selecting a tab scrolls the viewport smoothly and emits typed changes
PG3 focused tablist Arrow / Home / End arrows (RTL-aware) wrap, Home/End jump; focus and selection move together arrow keys, Home, and End move the selected segment with wrapping
PG4 swiping scroll quiet period / scrollend the page nearest the scroll position becomes active with reason scroll; programmatic scrolls never re-select; next/previous stop at the ends user swipes settle on the nearest page while programmatic scrolls are ignored
PG5 controlled / reduced motion parent change controlled values win and parent changes scroll the viewport; reduced motion scrolls with behavior: "auto" controlled pages scroll when the parent changes them and reduced motion scrolls instantly
PG6 invalid setup empty pages / missing root throws VIZE_UI_PAGER_PAGES or VIZE_UI_CONTEXT_MISSING: Pager rejects empty page lists and parts outside a Pager
PG7 SSR / hydration isolated requests byte-identical tabs and panels; the initial page is scrolled into place only after hydration renders byte-identical tabs and pages and hydrates without mismatches

The subpath ships no CSS.