VisorVisor
ComponentsFeedback

Offline Banner

App-shell banner for network connectivity loss. Non-blocking sticky bar below nav with offline, reconnecting, and restored states.

Overview

OfflineBanner is a full-width sticky banner pinned below the navigation bar. It signals network connectivity loss without blocking content — stale data remains visible and navigable beneath it.

It models three visual states driven by a useNetworkStatus() hook or controlled externally:

StateAppearance
offlineDark surface, Wifi-off icon, "You're offline", Retry button
reconnectingDark surface, spinner, "Reconnecting…", no Retry
restoredSuccess-tint surface, check icon, "Back online" (auto-dismisses after 1.5s)

When networkState is "online" the component renders nothing.

Interactive States

You're offline
App content remains visible and scrollable below the banner.

With useNetworkStatus Hook

The useNetworkStatus hook listens to window's online/offline events and drives the banner automatically. Pass a custom onRetry callback to run your own connectivity check (e.g., a lightweight HEAD request) instead of relying on navigator.onLine.

You're offline

Current network state: offline. Toggle your device's Wi-Fi to trigger the banner.

Custom Retry Check

Pass an onRetry function to useNetworkStatus to run a real connectivity probe before transitioning to restored:

const { networkState, retry } = useNetworkStatus({
  onRetry: async () => {
    try {
      await fetch('/api/health', { method: 'HEAD', cache: 'no-store' });
      return true;
    } catch {
      return false;
    }
  },
  restoredDisplayDuration: 2000, // show "Back online" for 2s
});

Installation

npx visor add offline-banner

This copies two files into your project:

  • components/ui/offline-banner/offline-banner.tsx — the component and hook
  • components/ui/offline-banner/offline-banner.module.css — the styles

Usage

import {
  OfflineBanner,
  useNetworkStatus,
} from '@/components/ui/offline-banner/offline-banner';

export function AppShell({ children }: { children: React.ReactNode }) {
  const { networkState, retry } = useNetworkStatus();

  return (
    <div>
      <nav>{/* navigation */}</nav>
      <OfflineBanner networkState={networkState} onRetry={retry} />
      <main>{children}</main>
    </div>
  );
}

Stale Data Pattern

When offline, stale data should remain visible beneath the banner — the banner only gates network-dependent actions (saves, fetches, mutations). Disable those buttons using the hook's networkState:

const { networkState, retry } = useNetworkStatus();
const isOffline = networkState === 'offline' || networkState === 'reconnecting';

<Button disabled={isOffline} onClick={save}>Save</Button>

API Reference

OfflineBannerProps

PropTypeDefaultDescription
networkState*'online' | 'offline' | 'reconnecting' | 'restored'Current network state. 'online' renders nothing; 'offline' shows the banner with a Retry button; 'reconnecting' shows a spinner; 'restored' shows a brief success confirmation before auto-dismissing.
onRetry() => voidCalled when the user presses the Retry button (offline state only). Typically wired to the `retry` function from `useNetworkStatus()`.
offlineLabelstring"You're offline"Text shown in the offline state.
reconnectingLabelstring"Reconnecting…"Text shown in the reconnecting state.
restoredLabelstring"Back online"Text shown in the restored state.
retryLabelstring"Retry"Label for the retry button.
classNamestringAdditional CSS class names to merge onto the banner element.

useNetworkStatus(options?)

function useNetworkStatus(options?: UseNetworkStatusOptions): UseNetworkStatusReturn

Options:

PropTypeDefaultDescription
onRetry() => Promise<boolean>Custom async connectivity check. Return true if online, false if still offline. Falls back to navigator.onLine when omitted.
restoredDisplayDurationnumber1500Milliseconds to show the "restored" state before auto-dismissing.

Returns:

FieldTypeDescription
networkStateNetworkStateCurrent state: "online" | "offline" | "reconnecting" | "restored"
retry() => voidTrigger a manual reconnect check. Transitions to "reconnecting" then "restored" or "offline".

Accessibility

  • The banner uses role="status" and aria-live="polite" — changes are announced non-interruptively.
  • aria-label updates to reflect the current state ("You're offline", "Reconnecting…", "Back online").
  • The Retry button has an aria-label that clarifies intent: "Retry — check network connection".
  • Icons are aria-hidden="true" — meaning is carried by the text label.
  • The banner does not trap focus or block interaction with content below it.

Composition Notes

  • Position the OfflineBanner immediately after your <nav> element so it sticks below the navigation bar without pushing content.
  • The component uses position: sticky; top: 0; z-index: 40 — ensure your nav bar sits above (z-index: 50 or higher) if both are sticky.
  • The entrance animation (200ms ease-out slide from top) is a CSS keyframe with opacity: 1 as resting state — SSR-safe, no JS mount dependency.