Componentfeedback0.99 KB gzipZero dependencies

Offline Banner

Offline banner listening to navigator.onLine with reconnecting state.

Preview

Live Preview

Disconnect your network to see the offline warning. It disappears after reconnection.

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add offline-banner

Or install the full npm package:

npm install bigbullui
import { OfflineBanner } from "@/components/ui/offline-banner";

<OfflineBanner />

Usage guidance

Use Offline Banner when a ticket stub surface needs Offline banner listening to navigator.onLine with reconnecting state. Keep props minimal and prefer composition with sibling stubs.

Avoid Offline Banner for long form flows or dense data grids where a dedicated layout block fits better.

Source code

offline-banner.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

/** Offline warning banner: monitors navigator.onLine state. */
export function OfflineBanner({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
  const [online, setOnline] = React.useState(true);
  const [reconnecting, setReconnecting] = React.useState(false);

  React.useEffect(() => {
    setOnline(navigator.onLine);
    const goOffline = () => setOnline(false);
    const goOnline = () => {
      setReconnecting(true);
      setTimeout(() => { setReconnecting(false); setOnline(true); }, 1200);
    };
    window.addEventListener("offline", goOffline);
    window.addEventListener("online", goOnline);
    return () => {
      window.removeEventListener("offline", goOffline);
      window.removeEventListener("online", goOnline);
    };
  }, []);

  if (online && !reconnecting) return null;

  return (
    <div className={cn("flex items-center justify-center gap-2 border-b px-4 py-1.5", online ? "border-success/40 bg-success/10" : "border-destructive/40 bg-destructive/10", className)} role="status" {...props}>
      <style>{`@keyframes obDots { 0%, 80%, 100% { opacity: 0.3; } 40% { opacity: 1; } }`}</style>
      {reconnecting ? (
        <>
          <span className="flex gap-0.5" aria-hidden="true">
            {[0, 1, 2].map((i) => (
              <span key={i} className="size-1 rounded-full bg-success" style={{ animation: "obDots 1s ease-in-out infinite", animationDelay: `${i * 150}ms` }} />
            ))}
          </span>
          <span className="text-xs text-success">Reconnecting to network…</span>
        </>
      ) : (
        <>
          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-destructive" aria-hidden="true"><path d="M1 1l22 22" /><path d="M16.72 11.06A10.94 10.94 0 0 1 19 12.55" /><path d="M5 12.55a10.94 10.94 0 0 1 5.17-2.39" /><path d="M10.71 5.05A16 16 0 0 1 22.58 9" /><path d="M1.42 9a15.91 15.91 0 0 1 4.7-2.88" /><path d="M12 20h.01" /></svg>
          <span className="text-xs text-destructive">You are offline — changes will not be saved</span>
        </>
      )}
    </div>
  );
}