suiss/ui · Interaction
Pull to Refresh
A scroll container that refreshes its content when the user pulls down from the top.
Pull to Refresh is an open-source interaction from suiss/ui for React, React Native, SwiftUI and Jetpack Compose. The @esuiss/ui CLI copies its source into your project, so you own the code.
PullToRefresh is a vertically scrolling container. When it is scrolled to the top, dragging down with touch, or scrolling up with a trackpad or wheel, pulls the content down with increasing resistance. An indicator above the content fills in spoke by spoke as the pull nears the threshold. Releasing past the threshold calls onRefresh, holds the content open, and swaps the indicator for the suiss/ui Spinner until the refresh finishes; a shorter pull springs back without refreshing.
The spinner stays visible for at least 600 ms so fast refreshes still read clearly. Use it for lists of fetched content, such as feeds and inboxes.
Installation
CLI
Works in any project with a components.json. See Installation for the one-line registry setup. Or use the suiss/ui CLI:
Manual
-
Add the
cnhelper. -
Add the pieces it builds on: Spinner.
-
Copy the source into
components/interactions/pull-to-refresh.tsx:
- Update the import paths to match your project.
Usage
- Give
PullToRefresha bounded height so its content scrolls inside it. onRefreshcan return a promise; the container stays in the refreshing state until it settles, whether it resolves or rejects.- Pulls only start at the top of the scroll position and are ignored while a refresh is in progress.
Examples
Empty list
An empty inbox refreshes from a trackpad pull and fills in with the first message.
API Reference
PullToRefresh
The scroll container that tracks the pull and shows the refresh indicator.
| Prop | Type | Default | Description |
|---|---|---|---|
onRefresh | () => Promise<unknown> | unknown | Required | Called when the user releases past the threshold; may return a promise. |
Also accepts the props of React.ComponentProps<"div">.
Accessibility
- The container sets
aria-busywhile refreshing. - The spinner is labelled
Refreshing; the pull indicator isaria-hidden. - Pulling requires touch or a scroll gesture, so offer another way to refresh where it matters, such as a button.
Notes
- The root exposes
data-slot="pull-to-refresh"anddata-phase(idle,pulling,refreshing,settling). The indicator and content usepull-to-refresh-indicatorandpull-to-refresh-content. - The
--pulland--pull-progresscustom properties track the pull distance and progress toward the threshold. - Transitions are disabled under
prefers-reduced-motion.

PullToRefresh is a ScrollView with a native RefreshControl. Pulling down past the top starts a refresh: a light impact haptic plays, the spinner shows, and onRefresh runs.
The spinner stays for at least 600 ms, even when onRefresh resolves sooner, so it does not flicker. It also hides if onRefresh throws or rejects. Use PullToRefreshControl to add the same behavior to a FlatList or SectionList.
Installation
Manual
- Install the dependencies:
-
Add the pieces it builds on: the primitives
components/primitives/haptics.tsx. -
Copy the source into
components/interactions/pull-to-refresh.tsx:
- Update the import paths to match your project.
- Install the dependencies:
-
Add the pieces it builds on: the primitives
components/primitives/haptics.tsx. -
Copy the source into
components/interactions/pull-to-refresh.tsx:
- Update the import paths to match your project.
-
Add the pieces it builds on: the primitives
components/primitives/haptics.tsx. -
Copy the source into
components/interactions/pull-to-refresh.tsx:
- Update the import paths to match your project.
-
Target iOS 17 or later.
-
Copy the theme files every view uses:
- Copy the source and every file it uses:
- Add the files to your app target.
- Set
minSdkto 24 or higher and add the dependencies:
- Copy the theme files every view uses:
- Copy the source and every file it uses:
- Update the
com.esuiss.uipackage names to match your project.
Usage
Pass an onRefresh that returns a promise, or nothing for synchronous work. All other ScrollView props pass through to PullToRefresh.
For lists, pass <PullToRefreshControl onRefresh={...} /> as the list's refreshControl. It manages its own refreshing state; pass refreshing to override it. Other RefreshControl props pass through.
Examples
With flat list
Uses PullToRefreshControl as the refreshControl of a FlatList.

API Reference
PullToRefreshControl
A RefreshControl for any list or scroll view.
| Prop | Type | Default | Description |
|---|---|---|---|
onRefresh | RefreshHandler | Required | Called when the user pulls to refresh; the spinner waits for the returned promise. |
refreshing | boolean | - | Overrides the internal refreshing state. |
Also accepts the props of RefreshControlProps.
PullToRefresh
A ScrollView with built-in pull to refresh.
| Prop | Type | Default | Description |
|---|---|---|---|
onRefresh | RefreshHandler | Required | Called when the user pulls to refresh; the spinner waits for the returned promise. |
Also accepts the props of React.ComponentProps<typeof ScrollView>.
Accessibility
PullToRefreshexposes abusystate while refreshing.PullToRefreshControlis labelledRefreshingwhile active.
Notes
- The haptic plays only if the app provides an adapter through
HapticsProvider. - The spinner uses the
muted-foregroundcolor, and on Android its background usescard, so both follow light and dark mode.

