Use .pull-to-refresh to let people fetch the latest items by
pulling down at the top of a list on a touch screen. A refresh button
inside the container does the same for everyone else.
Put a loading indicator in the
container as a direct child, then a <button data-refresh>,
then the content. AutoInit() starts every
.pull-to-refresh. A touch pull from the top brings the
indicator down with the finger. Releasing past the threshold starts a
refresh, and the indicator stays until the page says the refresh is done.
On a touch screen, pull the list below down; elsewhere, press Refresh.
- Build passed on main
- Invoice 2041 is ready
- Design review moved to Thursday
- New comment on the roadmap
- Your export finished
- Weekly usage report
- Reminder: update your password
- Two new sign-ups today
- Release notes draft
- Lunch on Friday?
<div class="pull-to-refresh" style="height: 24rem; overflow-y: auto">
<span class="loading-indicator contained" role="status" aria-label="Refreshing"></span>
<button type="button" class="text" data-refresh>Refresh</button>
<ul class="list">
<li>Inbox</li>
</ul>
</div>
Finishing a refresh
A refresh fires expressive:refresh from the container and runs
the onRefresh option. Pass the work's promise to
event.detail.waitUntil(). The indicator stays, and the
container has aria-busy="true", until every promise passed
in settles, whether it succeeds or fails. Or call complete()
when the work is done. Until one of those happens, the refresh keeps
running, and further pulls and presses do nothing. A rejection the
page does not catch still reaches unhandledrejection, and an
onRefresh that throws ends the refresh it started.
The script adds a visually hidden status region to the container. It
announces "Refreshing" when a refresh starts and "Refreshed" when it
finishes, so screen reader users hear both. Set the words with the
i18n option.
document.addEventListener('expressive:refresh', (event) => {
event.detail.waitUntil(loadLatest());
});
What scrolls
The container can scroll itself, with overflow-y: auto or
scroll, or sit in a page that scrolls. Either way, a pull
counts only at a scroll position of 0, and a drag that moves up or
sideways first scrolls as usual. So does a drag that a scrolled-down
scroller inside the container, or around it, can take. A second
finger ends a pull, so a pinch still zooms. The container has
overscroll-behavior-y: contain, and so does the page while it
holds one, so the browser's own pull-to-refresh does not also run. Don't
nest one .pull-to-refresh inside another.
Under reduced motion the indicator hides at once instead of sliding back up.
Options
| Name | Type | Default | Description |
|---|---|---|---|
threshold |
Number | 64 |
How far a finger must pull down, in pixels, for the release to refresh. A value that is not a positive number falls back to 64. |
onRefresh |
Function | null |
Called when a refresh starts, with the event's detail. |
i18n |
Object | { refreshing: 'Refreshing', refreshed: 'Refreshed' } |
What the status region announces when a refresh starts and when it finishes. |
Methods
const refresher = Expressive.PullToRefresh.getInstance(document.querySelector('.pull-to-refresh'));
refresher.refresh(); // starts a refresh; does nothing while one runs
refresher.complete(); // ends it: the indicator goes back up, aria-busy is removed
refresher.destroy(); // removes listeners and the status region, ends a refresh without an event
Tokens
Override these on the .pull-to-refresh.
| Token | Default |
|---|---|
--md-comp-pull-to-refresh-indicator-offset | 16px |
--md-comp-pull-to-refresh-motion | default spatial spring |
--md-comp-pull-to-refresh-motion-duration | the default spatial spring's duration |