Pull to refresh
A pull to refresh container lets people pull its content down to reload it, with a loading indicator that shows progress.
<md-gb-pull-to-refresh id="news" indicator-label="Refreshing news" style="block-size: 200px">
<ul>
<li>Markets rally on new data</li>
<li>City opens a new bike lane</li>
<li>Local team wins the final</li>
<li>Weather turns cooler this week</li>
<li>New library hours start Monday</li>
<li>Festival lineup announced</li>
</ul>
</md-gb-pull-to-refresh>const news = document.querySelector('#news');
const loadNews = () => new Promise((resolve) => setTimeout(resolve, 1500));
news.addEventListener('refresh', async () => {
await loadNews();
news.refreshing = false;
});Install with shadcn/ui: npx shadcn@latest add https://material-web.dev/3.0/r/md-pull-to-refresh.json (setup)
Pull to refresh is the pull-to-refresh behavior of the loading indicator, which it shows while the content reloads.
Usage guidelines
Link to “Usage guidelines”- Refresh content people expect to change. Use it on feeds and lists that update, such as news or messages.
- Always offer another way. Pulling isn't available to everyone, so provide a refresh button that people can use with a single pointer or the keyboard.
- Show it is working. The container keeps the indicator in view until you set
refreshingback tofalse.
This component is new in M3 Expressive.
Anatomy
Link to “Anatomy”A pull to refresh container holds the scrolling content (default slot). An indicator appears above it while people pull and while it refreshes.
- Water the plants
- Pay the rent
- Call the dentist
<md-gb-pull-to-refresh indicator-label="Refreshing tasks" style="block-size: 120px">
<ul>
<li>Water the plants</li>
<li>Pay the rent</li>
<li>Call the dentist</li>
</ul>
</md-gb-pull-to-refresh>Examples
Link to “Examples”Refresh button
Link to “Refresh button”Call refresh() from a button to start the same refresh without a gesture. It fires refresh and sets refreshing.
<md-gb-icon-button id="reload" aria-label="Refresh">
<md-gb-icon>refresh</md-gb-icon>
</md-gb-icon-button>
<md-gb-pull-to-refresh id="feed" indicator-label="Refreshing the feed" style="block-size: 160px">
<ul>
<li>New recipe: lemon pasta</li>
<li>Ten tips for sourdough</li>
<li>What to cook this weekend</li>
<li>Pantry staples to stock</li>
</ul>
</md-gb-pull-to-refresh>const feed = document.querySelector('#feed');
document.querySelector('#reload').addEventListener('click', () => feed.refresh());
feed.addEventListener('refresh', () => {
setTimeout(() => {
feed.refreshing = false;
}, 1500);
});Circular indicator
Link to “Circular indicator”Set indicator="circular" to use a circular arrow and progress indicator instead of the default contained loading indicator.
- Ali: Brunch this weekend?
- Sandra: Summer BBQ
- Trevor: Oui oui
- Britta: Recipe to try
<md-gb-pull-to-refresh indicator="circular" indicator-label="Refreshing messages" style="block-size: 160px">
<ul>
<li>Ali: Brunch this weekend?</li>
<li>Sandra: Summer BBQ</li>
<li>Trevor: Oui oui</li>
<li>Britta: Recipe to try</li>
</ul>
</md-gb-pull-to-refresh>Threshold and disabled
Link to “Threshold and disabled”Set threshold to the distance in pixels people must pull before releasing starts a refresh (the default is 80). Add disabled to turn pulling off.
- Order 1042: shipped
- Order 1041: delivered
- Order 1040: delivered
- Order 1039: returned
<md-gb-pull-to-refresh threshold="120" indicator-label="Refreshing orders" style="block-size: 160px">
<ul>
<li>Order 1042: shipped</li>
<li>Order 1041: delivered</li>
<li>Order 1040: delivered</li>
<li>Order 1039: returned</li>
</ul>
</md-gb-pull-to-refresh>Accessibility
Link to “Accessibility”- Pulling needs a drag, so also provide a button or another control that calls
refresh(), usable with a single pointer or the keyboard. - While refreshing, the indicator is a
progressbarnamed byindicator-label(the default is "Refreshing"), and the label is announced politely. Write a label that says what is refreshing, such as "Refreshing news".
Compatibility
Link to “Compatibility”Material Design 3No spec page
Material Design has no page for this component; it follows the general guidance (color, shape, motion, states).
BaselineWidely availableChrome 124Edge 124Firefox 125Safari 17.4
The component’s own CSS and JavaScript rely only on web platform features that are Baseline widely available: it works in Chrome 124, Edge 124, Firefox 125, Safari 17.4 and later.
Newer features it uses where the browser has them (progressive enhancements):
- overscroll-behavior (opens in a new tab) Limited availability degrades safely where it is missing
- scrollbar-color (opens in a new tab) Newly available degrades safely where it is missing
- scrollbar-gutter (opens in a new tab) Newly available degrades safely where it is missing
- scrollbar-width (opens in a new tab) Newly available degrades safely where it is missing
- touch-action (opens in a new tab) Widely available used inside an
@supportsguard - user-select (opens in a new tab) Limited availability used inside an
@supportsguard
Browser support Learn more about Baseline (opens in a new tab)
WAI-ARIAaxe and ARIA 1.3 pass
Roles the component renders: progressbar (opens in a new tab).
1 rendered state run through axe-core and the WAI-ARIA 1.3 role and attribute rules in a real browser, with no known gaps.
WAI-ARIA 1.3 (opens in a new tab) ARIA Authoring Practices (opens in a new tab) Accessibility
Lighthouse99 · 100 · 100 · 100
Performance Accessibility Best practices SEO
Audits under 90:
- Eliminate render-blocking resources
- Minify JavaScript
- Enable text compression
- Network dependency tree
- Render blocking requests
Lighthouse 12.8.2 (desktop, 2026-11-08) on the component alone: its first example on a page of its own, without this site's shell. The modules are the development build (not bundled or minified), so the performance score is indicative.
API reference
Link to “API reference”<md-gb-pull-to-refresh>
Link to “<md-gb-pull-to-refresh>”A Material Design component.
Properties
Link to “Properties”| Property | Attribute | Type | Default |
|---|---|---|---|
disabledWhether pulling is turned off (Compose enabled = false). |
disabled |
boolean |
false |
indicatorThe indicator: loading (M3 Expressive contained loading indicator) or circular (circular arrow and progress indicator). |
indicator |
PullToRefreshIndicator |
'loading' |
indicatorLabelAccessible label of the indicator, e.g. "Refreshing news". |
indicator-label |
string |
DEFAULT_LABEL |
refreshingWhether content is refreshing. Set by a pull past the threshold (or refresh()); set it back to false once the content has refreshed. |
refreshing |
boolean |
false |
thresholdHow far (px) the indicator must be pulled before releasing it refreshes (Compose PullToRefreshDefaults.PositionalThreshold, 80dp). |
threshold |
number |
PULL_TO_REFRESH_THRESHOLD_PX |
Slots
Link to “Slots”| Slot | Description |
|---|---|
| default | The scrolling content. |
Events
Link to “Events”| Event | Description |
|---|---|
refreshEvent |
Fired when a pull (or refresh()) starts a refresh. Cancel it to keep refreshing false. |
Methods
Link to “Methods”| Method | Description |
|---|---|
refresh() => boolean |
Starts a refresh without a gesture, e.g. from a refresh button: fires refresh and, unless it is cancelled, sets refreshing. |
CSS parts
Link to “CSS parts”| Part | Description |
|---|---|
::part(indicator) |
The indicator container. |
::part(scroller) |
The element that scrolls the content. |
CSS custom properties
Link to “CSS custom properties”| Property | Description |
|---|---|
--indicator-color |
|
--indicator-container-color |
|
--indicator-container-height |
|
--indicator-container-shape |
|
--indicator-container-width |
|
--indicator-elevation |
|
--indicator-max-distance |
How far the indicator moves below the top edge at the threshold. |
--motion-effects-duration |
|
--motion-effects-easing |
|
--motion-settle-duration |
|
--motion-settle-easing |