PWA UIv0.1 beta
Docs / Components / StackNavigator

Component

StackNavigator

StackNavigator animates a consumer-owned list of views while preserving covered DOM state, scroll, and focus. It never reads a URL, writes history, or chooses what to navigate to.

Preview

Installation

bash
pnpm dlx shadcn@latest add https://pwaui.com/r/stack-navigator.json

Usage

tsx
"use client"
import * as React from "react"import { NavigationBar } from "@/components/ui/navigation-bar"import { StackNavigator, useStackNavigator } from "@/components/ui/stack-navigator"
function ProjectDetail() {  const { pop } = useStackNavigator()
  return (    <>      <NavigationBar>        <NavigationBar.Leading>          <NavigationBar.BackButton data-autofocus onClick={pop} />        </NavigationBar.Leading>        <NavigationBar.Title>Project</NavigationBar.Title>      </NavigationBar>      <main>Project detail</main>    </>  )}
export function ProjectStack() {  const [detailOpen, setDetailOpen] = React.useState(false)  const entries = [    { key: "projects", label: "Projects", content: <button onClick={() => setDetailOpen(true)}>Open project</button> },    ...(detailOpen ? [{ key: "project", label: "Project detail", content: <ProjectDetail /> }] : []),  ]
  return <StackNavigator backGesture="auto" entries={entries} onPop={() => setDetailOpen(false)} />}

Anatomy

StackNavigatorstack viewedge-swipe layeruseStackNavigatorentriesonPop

Composition

See how PWAProvider, AppShell, SafeArea, NavigationBar, and TabBar divide viewport, scrolling, safe-area, and navigation responsibilities.

Read the app layout guide →

Behavior notes

  • entries is the complete controlled stack. Append to push and remove from the end to pop; the component keeps no private navigation history.
  • Covered views stay mounted but inert and aria-hidden, preserving uncontrolled input state and scroll without exposing hidden controls to keyboard or assistive-technology navigation.
  • useStackNavigator exposes depth, canPop, and pop so a NavigationBar.BackButton inside the top entry can request onPop.
  • backGesture defaults to auto: it enables only in standalone or fullscreen display mode. Use on only on a surface where you have verified that the browser or operating system does not own the leading edge; use off to disable it completely.
  • backGestureEdgeWidth defaults to 24 pixels and backGestureThreshold defaults to half the stack width. Tracking publishes --pwa-stack-swipe-progress and data-back-gesture-state without rerendering React on pointer moves.
  • onDepthChange can hide a persistent TabBar or update other shell chrome without querying the DOM.
  • Keep stacks shallow enough that retaining covered DOM remains inexpensive. Each view owns its own scroll viewport.

Platform limitations

  • Same-document View Transitions are a progressive enhancement. StackNavigator feature-detects the API and uses its transform/opacity fallback when unavailable; reduced-motion requests bypass sliding in either path.
  • Safari browser tabs already reserve the screen edge for browser history, so auto deliberately disables the custom gesture there. Installed iOS and Android behavior still requires device QA; auto is a conservative policy, not proof that every OS configuration leaves the edge free.
  • Android system Back does not arrive as this pointer gesture. Route hardware or system back through your router, then derive entries from that route state.
  • The gesture currently starts from the physical left edge. RTL leading-edge mirroring is a known follow-up; set backGesture to off for RTL surfaces that require a right-edge gesture.
  • The component cannot synchronize deep links or browser back on its own. When a router owns entries, map router-rendered state into the controlled array and route onPop back through that router.

Transition support

PlatformPathNotes
Chrome / Android WebViewNative enhancementSame-document View Transitions from Chrome 111; older engines use the CSS fallback.
Safari / iOSNative enhancementSame-document View Transitions from Safari 18; older versions use the CSS fallback.
FirefoxNative enhancementSame-document View Transitions from Firefox 139; older versions use the CSS fallback.

Accessibility

Only the active view is reachable. Push focuses an autofocus target or the new view container; pop returns focus to the pointer or keyboard trigger when it remains mounted, otherwise to the revealed view.