Skip to content
Bugwise
Menu
UI Components

Pull to Refresh

Drag a list down from the top and let go to load the newest items. Learn when it fits, how threshold, resistance and spinner states work, what should happen on errors and offline, and how testers break it.

35 min Content & layout iOS · Android · Web Also called: Swipe to refresh, Refresh control

You’re waiting for a refund. You open your banking app, pull the transactions list down, the spinner turns… and turns… and turns. Is the bank down? Is your internet down? Did the refund arrive? Nothing tells you. You close the app, open it again, and there it is: the refund was in the response all along, but the spinner never stopped.

Pull to refresh is one of the smallest components in an app, and one of the most used. It’s also full of timing bugs: double requests, endless spinners, duplicate items and lists that jump. Let’s learn it properly.

What is pull to refresh?

Pull to refresh lets people reload a list by dragging it down when it’s already scrolled to the top, then letting go. A spinner shows while new data loads, and fresh items appear at the top.

You’ll find it in:

  • Feeds: social posts, news, friends’ workouts.
  • Inboxes: mail, messages, notifications.
  • Time-sorted lists: bank transactions, orders and deliveries, flight status.

It’s a shortcut. Good apps also refresh automatically (when you open the screen, when the app returns from the background) and offer a visible way to refresh. Pulling is what impatient people do when they think something new should be there.

The four states

Every pull goes through the same states. Most bugs happen at the borders between them.

  1. 1

    Pulling

    The list follows your finger, with resistance. The indicator fills up to show how far is enough.

  2. 2

    Armed

    You passed the threshold. Letting go now will refresh. Often a haptic tick and "Release to refresh".

  3. 3

    Refreshing

    One request is running. The indicator spins and stays visible until it finishes.

  4. 4

    Done

    New items appear at the top, "Updated" time changes, and the indicator goes away. Or an error is shown.

On iOS the list itself moves down and the spinner sits in the gap. On Android the list stays still and a small disc slides down over it.

Threshold and resistance

Two numbers decide how a pull feels:

  • Threshold: how far you must pull before letting go triggers a refresh. Too low and it fires by accident while you scroll up; too high and people give up. Most apps need roughly 60–120 points of pull. The indicator usually “fills up” as you approach it, and many apps give a hapticHaptic feedbackA short vibration that confirms an action without the user needing to look at the screen.Open in glossary → tick when you pass it.
  • Resistance: the list moves less than your finger (often around half), so it feels like stretching a rubber band. Without resistance the list can be dragged halfway down the screen. With too much, the threshold feels unreachable.

One more rule matters: the pull only counts if the gesture started at the top. If you start scrolled down and flick back up to the top in one movement, that’s scrolling, not a request to refresh.

How it differs from similar patterns

PatternWhat it doesTypical use
Pull to refreshLoads newer items at the top on requestFeeds, inboxes, time-sorted lists
Refresh buttonThe same, without a gestureToolbars, desktop web, the accessible alternative
Auto refresh / live updatesNew items arrive by themselvesChats, live scores, order tracking
“New posts” pillTells you new items are waiting aboveLong feeds where you’ve scrolled down
infinite scrollInfinite scrollLoading more items automatically when the user reaches the bottom of a list.Open in glossary → / Load moreLoads older items at the bottomLong lists, search results

Try it: refresh lab

This news app does pull to refresh correctly on iOS and Android. Play with the threshold and resistance, then try to break it: pull during a slow refresh, scroll down and flick back to the top, switch to Server error and Offline, and use the refresh button instead of the gesture. Watch the network log.

9:41

Headlines

Updated 8:15

  • City council approves new bike lanes on the river road

    Metro Desk · 8:10

  • Rail strike called off after late-night talks

    Transport · 7:55

  • Heatwave warning for the weekend: what to know

    Weather · 7:30

  • Local bakery wins national sourdough award

    Food · 7:02

  • New ferry timetable starts on Monday

    Transport · 6:45

  • Library extends opening hours during exams

    Education · 6:20

  • Stadium concert: road closures and parking tips

    Metro Desk · 6:05

  • Startup week brings 40 free workshops to town

    Business · 5:50

Platform

Threshold

Resistance

Network

State: Idlehaptic

Pull: 0 px of 70 px needed. The red line is the threshold.

Network log

  • Pull down from the top of the list…

Try: pull during a slow refresh. Scroll down, then drag back up to the top in one go (it should not refresh). Switch to Server error and Offline. Use the refresh button with the keyboard.

Dev note

On iOS it’s a refresh control (UIRefreshControl in UIKit, .refreshable in SwiftUI). On Android it’s swipe to refresh (SwipeRefreshLayout for Views) or pull to refresh (PullToRefreshBox in Jetpack Compose Material 3). On the web, mobile browsers have their own pull to refresh that reloads the whole page.

When to use it

Pull to refresh is a good choice when:

  • The list is sorted by time, newest first, so “new” has an obvious place: the top.
  • Content changes often and people check back for updates: inbox, feed, order status.
  • The data might be stale even after auto refresh (on a weak connection, after a long time in the background).

Good and bad uses

Feeds, inboxes, transactions

A list of orders sorted by date. Pulling down checks for new deliveries and updates the statuses.

Forms and editable screens

A checkout form or a profile editor. A refresh would throw away what the user typed, and there’s nothing “new” to load. Scrolling up at the top of a long form shouldn’t risk that.

Newest at the top

A news list or a notification list, where new things appear above old ones.

Newest at the bottom

A chat conversation. New messages arrive at the bottom in real time, and pulling down at the top usually means “load older messages”. Pull to refresh there would confuse both users and code.

Simple vertical lists

A list that scrolls vertically and has nothing else that reacts to a downward drag.

Maps, canvases and sheets

A map pans when you drag down. A bottom sheet closes when you drag down. A drawing canvas draws. Adding pull to refresh to these makes gestures fight.

How good pull to refresh behaves

One request at a time

While a refresh is running, pulling again should not start a second request. The app either ignores the new pull or keeps showing the spinner. The same goes for a refresh while the screen is still doing its first load, or while it’s loading the next page at the bottom. Two overlapping requests are the classic source of duplicates and of a race conditionRace conditionA bug that depends on the timing or order of events, so it only happens sometimes.Open in glossary → where an older response overwrites a newer one.

New items, no duplicates, no jumps

  • When the user pulled from the top, the new items appear at the top, and the list shows them.
  • Items that were already there are not duplicated. The app should merge by item ID, not just stick the response on top.
  • Items that changed (an order went from “On the way” to “Delivered”) update in place.
  • The list doesn’t jump to a random position. If an app auto-refreshes while you’re reading further down, it should keep your place and show a “3 new posts” pill instead of moving the content under your thumb.

Errors stop the spinner

When the request fails, the spinner must stop. The old content stays on screen, and the user gets a clear, polite error messageError messageText that tells the user something went wrong and, ideally, how to fix it.Open in glossary →: a toast or an inline banner like “Couldn’t load new orders. Retry”. An endless spinner is the worst outcome: the user can’t tell whether to wait, retry or give up. Also check what happens on a timeoutTimeoutThe time limit after which an app stops waiting for a response and gives up.Open in glossary →: a request that never answers should end in an error after a reasonable time.

Offline is not an error you wait for

When the device is offline (airplane modeAirplane modeA device setting that turns off all wireless connections at once.Open in glossary →, no signal, a captive portalCaptive portalA Wi-Fi login page that intercepts traffic until the user signs in or accepts terms.Open in glossary →), good apps answer immediately: “You’re offline. Showing saved orders.” They keep the cached list and don’t pretend to load. See Network conditions for how to simulate this.

“Last updated” tells the truth

Many apps show “Updated 9:41” or “Updated just now” under the title or in the indicator. It must change after every successful refresh, and must not change after a failed one. A stale timestamp makes users distrust everything else on the screen.

It plays nicely with other gestures

Pulling only starts at the top. Horizontal swipes on rows (swipe actions), carousels inside the list, and the system’s own gestures must still work. On the web, the app’s pull to refresh must not fight the browser’s pull to refresh.

Common bugs

#BugWhat you seeTypical severity
1Endless spinnerOn an error or offline, the spinner never stops; no messageHigh
2Double requestPulling again during a refresh sends another requestMedium
3Duplicate itemsThe same posts or transactions appear twice after refreshingHigh
4List jumpsAfter a refresh you’re in the middle of the list, not at the new itemsMedium
5Fires by accidentNormal scrolling up to the top triggers a refresh (threshold too low or wrong start rule)Medium
6Never firesThe threshold is unreachable, or the pull doesn’t work on short lists that don’t scrollMedium
7Stale “Updated” timeStill says 8:15 after a successful refresh, or updates after a failed oneLow–Medium
8No alternativeNo refresh button or menu; keyboard, switch and voice users can’t refreshHigh (for affected users)
9Lost inputA refresh wipes a half-typed comment or a filter the user setMedium–High
10Browser reloads the pageOn a mobile website, pulling reloads the whole page instead of the listMedium

Find the bug

This fitness app shows your friends’ activity with pull to refresh, and it has six bugs. Each request takes about 2.5 seconds. Explore it like a tester:

  • Pull to refresh once and watch the list, the spinner, the header and the network log.
  • Pull again while the spinner is turning.
  • Scroll down a bit, then drag the list back up to the top in one movement.
  • Use the device panel: turn on server errors or airplane mode, then pull.
  • Try to refresh without dragging.

🐞 Bug hunt: Activity feed

This demo has 6 hidden bugs. Explore it like a tester, then check your findings.

Found0/6
9:41

Activity

Friends · Updated 8:15

  • EEmma ran 5.2 km28:14 · Riverside loop8:02
  • DDaniel cycled 22 km54:10 · Canal path7:48
  • SSofia swam 1.5 km36:40 · City pool7:30
  • LLeo hiked 9.8 km2:41:00 · Green hills7:05
  • MMia ran 10 km52:31 · Park run6:50
  • MMarco cycled 41 km1:32:00 · Coast road6:31
  • AAnna walked 4.1 km45:12 · Old town6:10
  • TTom swam 800 m22:05 · LakeYesterday
  • LLena ran 7.4 km39:50 · Hill repeatsYesterday
  • OOmar hiked 14 km4:05:00 · Ridge trailYesterday

Device

Drag the list with your mouse or finger to scroll and to pull down at the top. Each request takes about 2.5 s.

Network log

  • Pull to refresh to see API requests…
  1. 1

    Hint: Turn on server errors (or airplane mode) and pull to refresh.

  2. 2

    Hint: Pull, then pull again before the first refresh finishes. Watch the network log and the top of the feed.

  3. 3

    Hint: Pull once from the top and watch where you end up when the new items arrive.

  4. 4

    Hint: Scroll down a little, then drag the list down to scroll back up, past the top.

  5. 5

    Hint: Look for a refresh button or menu item. Try with the keyboard only.

  6. 6

    Hint: Refresh successfully and read the header.

How to test it

Checklist

Pull to refresh test checklist

0 of 14 checked

Example test cases

TC-PTR-001Pull past the threshold loads new items

Preconditions

  • Server has 3 new activities
  • Feed is scrolled to the top

Steps

  1. Pull the feed down past the threshold
  2. Release
  3. Wait for the spinner to disappear

Expected result

Exactly one request is sent. The 3 new activities appear at the top, the list stays at the top, and the header says "Updated just now".

TC-PTR-002Short pull does not refresh

Steps

  1. Scroll to the top
  2. Pull down about 1 cm and release

Expected result

The list snaps back. No request is sent.

TC-PTR-003Pulling during a refresh sends no second request

Preconditions

  • Network throttled to "Slow 3G" or a proxy breakpoint holds the response

Steps

  1. Pull to refresh
  2. While the spinner is visible, pull to refresh again
  3. Release the response

Expected result

Only one feed request is sent. New items appear once; nothing is duplicated.

TC-PTR-004Server error stops the spinner and offers Retry

Preconditions

  • Proxy tool returns HTTP 500 for the feed request

Steps

  1. Pull to refresh
  2. Wait 5 seconds

Expected result

The spinner stops. The previous items are still shown. A message like "Couldn't refresh. Retry" appears, and "Updated" keeps its old time.

TC-PTR-005Offline refresh answers immediately

Preconditions

  • Airplane mode is on

Steps

  1. Pull to refresh

Expected result

Within a second the app says it is offline and keeps showing cached content. No endless spinner.

TC-PTR-006Scrolling back to the top does not refresh

Steps

  1. Scroll the feed down by about one screen
  2. Drag (or flick) the list down to scroll back up, overshooting the top

Expected result

The list stops at the top (maybe with a small bounce). No request is sent.

TC-PTR-007Refresh is possible without the gesture

Preconditions

  • Voice Control (iOS) or a hardware keyboard is used

Steps

  1. Open the feed
  2. Activate the Refresh button (say "Tap Refresh" or Tab to it and press Enter)

Expected result

The feed refreshes exactly like after a pull, and the result is announced.

TC-PTR-008Mobile web: browser does not reload the page

Preconditions

  • Chrome on Android
  • Web app has its own pull to refresh on the feed

Steps

  1. Scroll the feed to the top
  2. Pull down past the threshold

Expected result

Only the app's own refresh runs. The browser does not reload the page, and typed text elsewhere on the page is kept.

Tip: click a test case to expand it.

Tools

Accessibility

Pull to refresh is a dragging gesture, so some people can’t do it at all: people with motor impairments, people using Switch Control, Voice Control or a keyboard. Check these:

  • A visible alternative: a Refresh button in the toolbar, a menu item, or automatic refresh when the screen opens. WCAGWCAGThe Web Content Accessibility Guidelines, the main international standard for accessibility.Open in glossary → 2.5.1 Pointer Gestures (Level A) and 2.5.7 Dragging Movements (Level AA) ask for a way to do gesture and drag actions with a simple tap or click instead.
  • Screen readers: VoiceOverScreen readerSoftware that reads the screen aloud for blind and low-vision users, like VoiceOver or TalkBack.Open in glossary → users scroll with three fingers. With a standard iOS refresh control, a three-finger swipe down at the top of the list often triggers the refresh, but not reliably, and custom implementations often don’t support it at all. TalkBack users need a Refresh button or action. Test the real thing, not the documentation.
  • Announce the state: “Refreshing”, then the result: “3 new stories” or “Couldn’t refresh”. The spinner needs an accessible label, and the result should be announced without moving focus.
  • Don’t move focus: when new items arrive at the top, focus should stay on the item the user was on.
  • Reduced motion: with Reduce Motion on, the refresh should still work and be visible, just with less animation.
  • Keyboard (web): the Refresh button is reachable with Tab, has a visible focus ring and a clear name (“Refresh orders”, not just an icon).

Mobile vs web

  • The spinner stays until the work finishes, so a request that never ends means a spinner that never ends.
    Dev note
    Built in: UIRefreshControl (UIKit) and .refreshable (SwiftUI). With .refreshable, the spinner stays until the async work finishes.
  • The list moves down and the spinner sits in the gap; a haptic tick can mark the threshold.
  • Test with VoiceOver’s three-finger scroll, Voice Control and Reduce Motion.
Dev note
Built in: SwipeRefreshLayout (Views) and PullToRefreshBox (Compose Material 3).
  • A small circular indicator slides down over the list; the list itself usually stays put. Android 12+ also shows a “stretch” overscroll effect elsewhere.
  • Check that the indicator doesn’t clash with the top app bar or tabs, and that TalkBack users have a Refresh action.
  • Mobile browsers have their own pull to refresh that reloads the whole page (Chrome on Android, Safari on iOS 15 and later).
  • A web app with its own pull to refresh has to stop the browser’s version. Support differs between browsers and versions, so test on a real iPhone and Android phone.
    Dev note
    It uses the CSS overscroll-behavior property (for example overscroll-behavior-y: contain).
  • Desktop has no pull at all: the page needs a visible Refresh button (and browser reload must not lose the user’s work).

Check your understanding

Quick quiz

0/5 answered · 0 correct

  1. 1.Where does pull to refresh fit best?

  2. 2.You pull twice quickly and every new post appears twice. What is the most likely cause?

  3. 3.The server returns an error during a refresh. What should the user see?

  4. 4.You scroll down, then drag the list back up to the top in one movement, and it refreshes. Which rule is broken?

  5. 5.Why does a pull-to-refresh screen need a visible Refresh button too?

Key takeaways

  • Pull to refresh reloads time-sorted lists (feeds, inboxes, transactions); avoid it on forms, maps, sheets and bottom-anchored chats.
  • It moves through pulling → armed → refreshing → done (or error); the pull must start at the top and pass a deliberate threshold.
  • Test the timing: one request at a time, no duplicates, no jumps, and a spinner that always stops, including on errors and offline.
  • “Last updated” must tell the truth.
  • Always provide a visible Refresh and announce the result for screen reader users.

Finished this page?Nice work, this page is complete!

Mark it as done to track your progress. It's saved in this browser, no account needed.

Its review cards are now in Bites ⚡
Esc

Type to search the whole site.