Skip to content
Native Animation
Offline Retry Banner animation preview

This preview uses fictional demo content. See the props below for the customization options available in this component.

npx nativeanimation add offline-retry-banner

Offline Retry Banner

An offline feed with retry feedback and a reconnection reveal, with configurable articles, status labels, accent color, and fonts.

$9 for this animation

Add to your project

Copy the component and its assets into your app with the Native Animation CLI, or paste the agent prompt into Claude Code, Codex or Cursor once the MCP server is connected.

About this animation

An offline feed with retry feedback and a reconnection reveal, with configurable articles, status labels, accent color, and fonts.

Why this one

Most connection notices stop at a generic error. This one explains what is still available: saved stories remain readable while unsaved cards fade to 45%. Three Lucide Wi-Fi arcs light up in sequence during retry. Recovery turns the actual banner green, draws a check, folds the surface into a departing pill, restores the cards in a top-to-bottom cascade, and inserts a fresh story that pushes the list down.

How it works

  1. A ref exposes goOffline(), retry(), restore(), and reset(); React holds only discrete status and container layout.
  2. Shared values drive banner presence, contraction, connectivity arcs, countdown, and story insertion on the UI thread. Each card derives its dimming target from the connection progress and delays both directions by 50 ms per position.
  3. onRetry resolves true for recovery or false for failure. Rejections are failures too. A generation token discards stale requests when a transition interrupts them.
  4. Failure shakes the material and starts a real five-second countdown. A retry can interrupt it immediately; reaching zero retries automatically. Requests and timers are cleaned up on unmount.
  5. The countdown holds at each integer and rolls only in the final 180 ms of a second. Reduced motion uses static figures and 200 ms fades instead of flights, shakes, rotation, or scaling.
  6. Article images are supplied by the host, and the scrollable feed measures its own container with onLayout.

Install

npx expo install @shopify/react-native-skia@2.14.0 expo-image@~58.0.17 react-native-gesture-handler@~3.3.0 react-native-reanimated@4.7.1 react-native-worklets@0.13.0

Copy OfflineRetryBanner.tsx, icons.ts, and index.ts together. Wrap the app in GestureHandlerRootView. For web, load CanvasKit before mounting this component (Expo/Skia's WithSkiaWeb or the host's existing lazy Skia loader). The component uses Skia and must not be statically mounted before that loader is ready. Fonts are optional host props, with system fonts as the fallback.

Icons: Lucide (ISC). Paths are copied from the project's kit. The Lumen tile and editorial photography belong only to the demo, which additionally uses expo-linear-gradient.

Usage

import { useRef } from 'react';
import { OfflineRetryBanner, type OfflineRetryBannerRef } from './offline-retry-banner';

const connection = useRef<OfflineRetryBannerRef>(null);

<OfflineRetryBanner
  ref={connection}
  articles={articles}
  onRetry={async () => {
    try {
      return (await fetch('https://your-service.example/health')).ok;
    } catch {
      return false;
    }
  }}
/>;

// Call from the application's network subscription:
connection.current?.goOffline();

A personalized variant:

<OfflineRetryBanner
  ref={connection}
  articles={articles}
  newArticle={latestStory}
  accentColor="#0EA5E9"
  successColor="#16A34A"
  retrySeconds={5}
  colorScheme="dark"
  fontFamily="YourInterfaceFont"
  titleFontFamily="YourEditorialFont"
  monoFontFamily="YourNumberFont"
  onRetry={checkConnection}
  onArticlePress={openStory}
  onStatusChange={handleConnectionStatus}
/>

The host owns connectivity, actual caching, article retrieval, and navigation. This component displays availability and blocks onArticlePress for unsaved stories while offline; it does not download or persist articles.

Props

PropTypeDefaultPurpose
articlesreadonly OfflineArticle[][]Articles to display.
newArticleOfflineArticleundefinedOptional story inserted after recovery.
onRetry() => Promise<boolean>undefinedResolve true for recovery, false for failure; rejection also means failure.
onStatusChange( status: ConnectionStatus, ) => voidundefinedDiscrete connection state callback.
onArticlePress( article: OfflineArticle, ) => voidundefinedCalled when a readable article is pressed.
onInteraction() => voidundefinedCalled for touch or gesture input, allowing the host to stop autoplay.
fontFamilystringundefinedInterface font; defaults to the system font.
titleFontFamilystringundefinedEditorial headline font; defaults to the system font.
monoFontFamilystringundefinedCountdown font; defaults to the system font.
accentColorstring'#0EA5E9'Accent used for saved stories and fresh content.
successColorstring'#16A34A'Recovery color.
retrySecondsnumber5Automatic retry delay in seconds, clamped to 1–9.
colorScheme'light' | 'dark' | 'auto''auto'Light, dark, or device palette.
offlineLabelstring"You're offline — showing saved articles"Offline explanation.
restoredLabelstring'Back online'Recovery announcement.
retryLabelstring'Retry'Retry button and accessibility label.
newStoriesLabelstring'2 new stories'Label on the inserted story.
styleStyleProp<ViewStyle>undefinedContainer style.
testIDstringundefinedRoot test identifier.
durationnumber760Retry spinner cycle in milliseconds; request timing is unchanged. Spring physics and reduced-motion timing are unchanged.

Demo timeline

Online at 0 ms → offline at 1000 → first retry at 1570 → failure at 1790 → manual second retry at 2350 → recovery at 2570. The check draws and the fresh story pushes into the feed before 3000 ms. Contraction starts 580 ms after recovery; the status returns to online 1080 ms after recovery. The full cycle repeats every 7600 ms so the recovered feed has a readable rest. Any touch stops playback. The second demo retry is deliberately manual: the complete five-second automatic countdown remains available during manual use.

The demo includes Light/Dark controls, a connection simulation button, and a local reading panel. All three photographs are bundled. No network is needed to demonstrate either saved reading or connection recovery.

Compatibility

Expo SDK 58; React Native 0.88.0; Reanimated 4.7.1; worklets 0.13.0; Skia 2.14.0. Intended for iOS, Android, and web. Compatible with the project's SDK 58 Expo Go runtime; custom native configurations require a development build.

Accessibility

Retry and article controls have button roles and labels. A swipe on the status icon retries; the Retry button provides the equivalent tap action. Important connection transitions announce through AccessibilityInfo and a polite live region. Countdown digits are hidden inside a single accessible group to avoid announcing each rolling glyph. Saved cards remain readable offline. The demo theme control has switch semantics. Touch targets are at least 44 pt. Reduced motion removes translations, shaking, sway, rolling digits, repeated waves, and press scaling, using 200 ms fades for state changes.

Implementation notes

Layout: the header reserves the top-right viewer-close area. Headline/image proportions reserve enough room for two title lines and metadata. At 390×844 and 320×694, content remains in a scrollable feed rather than compressing all cards to fit; smaller screens require scrolling to the final story. The recovery begins at 2.57 seconds. Effects are actual surface color, path opacity/drawing, material shadow, and layout changes; no block sweeps over text.