Pull to Refresh Web Component

npm version Build Status

A lightweight, customizable Web Component that adds pull-to-refresh functionality to your web applications. Perfect for mobile-first experiences and progressive web apps.

Demo

Live Demo (Source)

Additional Demos

Features

TypeScript & Framework Support

Quick Start

Installation

npm install @aarongustafson/pull-to-refresh

Usage

Option 1: Manual registration

import { PullToRefreshElement } from '@aarongustafson/pull-to-refresh';

customElements.define('pull-to-refresh', PullToRefreshElement);

Option 2: Auto-define (browser environments only)

import '@aarongustafson/pull-to-refresh/define.js';
// Registers <pull-to-refresh> when customElements is available

Prefer to control when registration happens? Call the helper directly:

import { definePullToRefresh } from '@aarongustafson/pull-to-refresh/define.js';

definePullToRefresh();

You can also include the guarded script from HTML:

<script src="./node_modules/@aarongustafson/pull-to-refresh/define.js" type="module"></script>

Basic Example

<pull-to-refresh>
  <div class="content">
    <h1>My Content</h1>
    <p>Pull down from the top to refresh!</p>
  </div>
</pull-to-refresh>

<script type="module">
  import '@aarongustafson/pull-to-refresh';

  const ptr = document.querySelector('pull-to-refresh');

  ptr.addEventListener('ptr:refresh', (e) => {
    // Perform your refresh logic (fetch data, etc.)
    fetch('/api/data')
      .then(response => response.json())
      .then(data => {
        // Update your content
        updateContent(data);
        // Signal completion
        e.detail.complete();
      });
  });
</script>

API

Attributes

Attribute Type Default Description
threshold number 80 Distance in pixels to trigger refresh
indicator-text string localized Text shown when pulling down
release-text string localized Text shown when ready to release
refreshing-text string localized Text shown while refreshing
lang string auto-detected Language code for localization
disabled boolean false Disables pull-to-refresh functionality
disable-selection boolean false Prevents text selection during pull gesture

Note: The text attributes use automatic localization based on the lang attribute or auto-detection. See Localization below.

Properties

All attributes are also available as properties:

const ptr = document.querySelector('pull-to-refresh');
ptr.threshold = 120;
ptr.indicatorText = 'Swipe down';
ptr.disabled = true;

Events

Event Detail Description
ptr:pull-start {} Fired when pull gesture starts
ptr:pull-move { distance: number } Fired during pull gesture
ptr:pull-end {} Fired when pull gesture ends
ptr:refresh { complete: Function } Fired when refresh is triggered
ptr:refresh-complete {} Fired when refresh completes

Important: Call event.detail.complete() in your ptr:refresh handler to signal completion:

ptr.addEventListener('ptr:refresh', (e) => {
  doAsyncWork().then(() => {
    e.detail.complete(); // Call this when done
  });
});

If you don't call complete(), the component will auto-complete after 2 seconds.

Slots

Slot Description
(default) Your scrollable content
indicator Optional custom indicator element

CSS Custom Properties

Property Default Description
--ptr-indicator-height 3.125rem Height of the indicator area
--ptr-indicator-bg ButtonFace Background color of indicator
--ptr-indicator-color ButtonText Text color of indicator
--ptr-indicator-font-size 0.875rem Font size of indicator text
--ptr-transition-duration 0.2s Duration of indicator transitions

Methods

Method Description
completeRefresh() Manually complete the refresh (alternative to calling event.detail.complete())

Examples

Custom Threshold

<pull-to-refresh threshold="120">
  <div>Content here</div>
</pull-to-refresh>

Localization

The component automatically detects the language and displays localized text:

<!-- Spanish -->
<pull-to-refresh lang="es">
  <div>Contenido aquí</div>
</pull-to-refresh>

<!-- French -->
<pull-to-refresh lang="fr">
  <div>Contenu ici</div>
</pull-to-refresh>

<!-- Japanese -->
<pull-to-refresh lang="ja">
  <div>ここにコンテンツ</div>
</pull-to-refresh>

The component supports 16 languages with automatic fallback to English. Language is detected from:

  1. The element's lang attribute
  2. The nearest ancestor's lang attribute
  3. The document's lang attribute
  4. Default: English

You can also register custom translations:

import { PullToRefreshElement } from '@aarongustafson/pull-to-refresh';

PullToRefreshElement.registerTranslations({
  'pt-BR': {
    indicator: '↓ Puxe para atualizar',
    release: '↻ Solte para atualizar',
    refreshing: '⏳ Atualizando...'
  }
});

Custom Styling

<style>
  pull-to-refresh {
    --ptr-indicator-bg: #f0f0f0;
    --ptr-indicator-color: #1976d2;
    --ptr-indicator-font-size: 1rem;
    --ptr-indicator-height: 3.75rem;
  }
</style>

<pull-to-refresh>
  <div>Content here</div>
</pull-to-refresh>

Custom Messages

<pull-to-refresh
  indicator-text="⬇ Swipe down"
  release-text="🔄 Let go!"
  refreshing-text="⏳ Loading...">
  <div>Content here</div>
</pull-to-refresh>

With Fetch API

ptr.addEventListener('ptr:refresh', async (e) => {
  try {
    const response = await fetch('/api/latest');
    const data = await response.json();
    renderData(data);
  } catch (error) {
    console.error('Refresh failed:', error);
  } finally {
    e.detail.complete();
  }
});

Disabled State

<!-- Disable pull-to-refresh when needed -->
<pull-to-refresh disabled>
  <div>No refresh available</div>
</pull-to-refresh>

<script>
  // Or toggle programmatically
  ptr.disabled = true;
</script>

Tracking Pull Distance

ptr.addEventListener('ptr:pull-move', (e) => {
  console.log('Pull distance:', e.detail.distance);
  // Use this for custom animations, etc.
});

Localization (i18n)

The component includes built-in translations for 16 languages:

Language Code Indicator Text
English en ↓ Pull to refresh
Chinese (Mandarin) zh ↓ 下拉刷新
Hindi hi ↓ रीफ्रेश करने के लिए खींचें
Spanish es ↓ Desliza para actualizar
French fr ↓ Tirez pour actualiser
Arabic ar ↓ اسحب للتحديث
Bengali bn ↓ রিফ্রেশ করতে টানুন
Portuguese pt ↓ Puxe para atualizar
Russian ru ↓ Потяните для обновления
Japanese ja ↓ 引っ張って更新
German de ↓ Zum Aktualisieren ziehen
Punjabi pa ↓ ਤਾਜ਼ਾ ਕਰਨ ਲਈ ਖਿੱਚੋ
Javanese jv ↓ Tarik kanggo nyegerake
Korean ko ↓ 당겨서 새로고침
Vietnamese vi ↓ Kéo để làm mới
Italian it ↓ Trascina per aggiornare

Language Detection

The component uses a cascading fallback approach:

  1. Element's lang attribute: &lt;pull-to-refresh lang="es"&gt;
  2. Nearest ancestor with lang: &lt;div lang="fr"&gt;&lt;pull-to-refresh&gt;
  3. Document language: &lt;html lang="de"&gt;
  4. Default: English (en)

Regional variants (e.g., en-US, es-MX, fr-CA) automatically fall back to their base language.

Custom Translations

Register custom translations or override existing ones:

import { PullToRefreshElement } from '@aarongustafson/pull-to-refresh';

// Add a new language
PullToRefreshElement.registerTranslations({
  'nl': {
    indicator: '↓ Trek om te vernieuwen',
    release: '↻ Loslaten om te vernieuwen',
    refreshing: '⏳ Vernieuwen...'
  }
});

// Override existing translations
PullToRefreshElement.registerTranslations({
  'en': {
    indicator: '⬇ Pull down',
    release: '🔄 Let go',
    refreshing: '⏳ Loading...'
  }
});

Per-Instance Overrides

You can always override translations for individual instances:

<pull-to-refresh
  lang="es"
  indicator-text="Custom Spanish text"
  release-text="Custom release text">
  <!-- Content -->
</pull-to-refresh>

Testing

npm test               # Run tests in watch mode
npm run test:run       # Run tests once
npm run test:ui        # Open Vitest UI
npm run test:coverage  # Generate coverage report

Development

npm install            # Install dependencies
npm run setup          # Run setup wizard (first time)
npm run lint           # Lint code
npm run format         # Format code

Browser Support

Works in all modern browsers supporting:

For legacy browsers, use appropriate polyfills.

Package Exports

// Auto-define
import '@aarongustafson/pull-to-refresh';

// Class only
import { PullToRefreshElement } from '@aarongustafson/pull-to-refresh/pull-to-refresh.js';

// Both
import { PullToRefreshElement } from '@aarongustafson/pull-to-refresh';

// Manual define script
import '@aarongustafson/pull-to-refresh/define.js';

Contributing

Contributions welcome! See CONTRIBUTING.md for guidelines.

License

MIT - See LICENSE

Credits

Created by Aaron Gustafson


Try it out! Check out the live demo 🚀