PWA UIv0.1 beta
Docs / Components / PullToRefresh

Component

PullToRefresh

PullToRefresh owns a scroll viewport, arms only at its top edge, and keeps the refresh indicator visible until your asynchronous refresh work settles.

Preview

Inbox

Pull the list down to refresh · 0 complete

Pull to refresh
Design reviewUpdated 2 minutes ago
Release checklistUpdated 3 minutes ago
Device QAUpdated 4 minutes ago
Offline statesUpdated 5 minutes ago
Install flowUpdated 6 minutes ago

Installation

bash
pnpm dlx shadcn@latest add https://pwaui.com/r/pull-to-refresh.json

Usage

tsx
"use client"
import { PullToRefresh } from "@/components/ui/pull-to-refresh"
export function Inbox() {  async function refresh() {    await fetch("/api/messages", { cache: "no-store" })  }
  return (    <PullToRefresh className="h-full" onRefresh={refresh}>      <ol>{/* messages */}</ol>    </PullToRefresh>  )}

Anatomy

PullToRefreshindicatorscroll viewportcontent

Composition

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

Read the app layout guide →

Behavior notes

  • The component owns its scroll viewport so it can determine reliably when scrollTop is zero.
  • Inside AppShell, place PullToRefresh in AppShell.Main and move vertical overflow from Main to PullToRefresh: use overflow-hidden on Main and h-full on PullToRefresh.
  • The state contract is exposed through data-state=idle, pulling, armed, or refreshing and --pwa-pull-distance.
  • Mouse dragging is ignored. Desktop users refresh through your ordinary visible controls or browser command.
  • Use the controlled refreshing prop when another state owner determines when the refresh has finished.

Platform limitations

  • Browser-mode Safari and Chrome may already provide page-level pull-to-refresh. Keep the application root overscroll-contained so the browser gesture does not compete with this viewport; installed standalone PWAs generally need the custom control.
  • Pointer-derived touch behavior still varies at platform boundaries. Verify both browser and installed display modes on real iOS and Android hardware before treating the gesture as stable.

Accessibility

Refresh progress is announced through a polite status live region. The default spinner stops rotating when reduced motion is requested; keep a separate visible refresh action available for users who cannot perform the gesture.