# Pull-to-refresh

> Refreshes a list or page when a person pulls down at its top, or presses its refresh button.

## Anatomy

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](/loading-indicator.html.md) 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.

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());
});
```

## The refresh button

Pulling needs a touch screen, so every container needs its `<button data-refresh>`. Keyboard, mouse and switch users refresh with it, and a semantics rule flags a container without one. A mouse drag never pulls. The `refresh()` method starts the same refresh from script.

## 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 |
