Error Offline
Original · freeoffline / no-connection state
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/error-offline.json"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import { motion, useReducedMotion } from "motion/react";
type Status = "offline" | "checking" | "online";
type Step = { title: string; detail: string };
type QueuedItem = { title: string; meta: string; kind: string };
type Cached = { name: string; note: string };
const AUTO_RETRY_SECONDS = 15;
const TROUBLESHOOT: Step[] = [
{
title: "Check Wi‑Fi and mobile data",
detail:
"Open your device network settings and confirm Wi‑Fi is on and joined to a network, or that mobile data is enabled with usable signal.",
},
{
title: "Turn airplane mode off",
detail:
"Airplane mode disables every radio at once. Toggle it off, wait a few seconds, and let the connection re‑establish before retrying.",
},
{
title: "Restart your router",
detail:
"Unplug the router, wait a full 30 seconds, then plug it back in. Give it up to a minute to negotiate a fresh connection.",
},
{
title: "Move closer to the access point",
detail:
"Thick walls and distance weaken the signal. Step nearer to your router, or to a spot where the network has been reliable before.",
},
];
const QUEUED: QueuedItem[] = [
{ title: "Reply to Priya Menon", meta: "Draft saved 2 min ago", kind: "Message" },
{ title: "invoice_0426.pdf", meta: "1.2 MB · upload queued", kind: "File" },
];
const CACHED: Cached[] = [
{ name: "Dashboard", note: "Cached 4 min ago" },
{ name: "Saved articles", note: "18 items available" },
{ name: "Account settings", note: "Read‑only offline" },
];
const CSS = `
@keyframes eof-float { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-8px)} }
@keyframes eof-scan { 0%,100%{opacity:.2} 50%{opacity:1} }
@keyframes eof-spin { to{transform:rotate(360deg)} }
@keyframes eof-ping { 0%{transform:scale(.7);opacity:.55} 100%{transform:scale(2);opacity:0} }
@keyframes eof-bar { 0%,100%{transform:scaleY(.35)} 50%{transform:scaleY(1)} }
@keyframes eof-pop { 0%{transform:scale(.4);opacity:0} 60%{transform:scale(1.08)} 100%{transform:scale(1);opacity:1} }
@keyframes eof-draw { from{stroke-dashoffset:120} to{stroke-dashoffset:0} }
.eof-float{animation:eof-float 6s ease-in-out infinite}
@media (prefers-reduced-motion: reduce){
.eof-anim, .eof-float{animation:none !important}
}
`;
function fmt(total: number): string {
const m = Math.floor(total / 60)
.toString()
.padStart(2, "0");
const s = (total % 60).toString().padStart(2, "0");
return `${m}:${s}`;
}
function IconRefresh({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
<path d="M3 12a9 9 0 0 1 15.5-6.2L21 8" />
<path d="M21 3v5h-5" />
<path d="M21 12a9 9 0 0 1-15.5 6.2L3 16" />
<path d="M3 21v-5h5" />
</svg>
);
}
function IconClock({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
<circle cx="12" cy="12" r="9" />
<path d="M12 7v5l3 2" />
</svg>
);
}
function IconChevron({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
<path d="m6 9 6 6 6-6" />
</svg>
);
}
function IconArrow({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
<path d="M5 12h14" />
<path d="m13 6 6 6-6 6" />
</svg>
);
}
function IconBolt({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
<path d="M13 2 4 14h7l-1 8 9-12h-7l1-8Z" />
</svg>
);
}
function SignalBars({ status, reduce }: { status: Status; reduce: boolean }) {
const active = status === "online";
const scanning = status === "checking";
return (
<span className="inline-flex h-4 items-end gap-[3px]" aria-hidden="true">
{[0, 1, 2, 3].map((i) => (
<span
key={i}
className={`eof-anim w-[3px] origin-bottom rounded-sm ${
active
? "bg-emerald-500"
: scanning
? "bg-indigo-400"
: "bg-slate-300 dark:bg-slate-700"
}`}
style={{
height: `${6 + i * 3}px`,
animation: scanning && !reduce ? "eof-bar 1s ease-in-out infinite" : undefined,
animationDelay: `${i * 0.12}s`,
}}
/>
))}
</span>
);
}
export default function ErrorOffline() {
const reduce = useReducedMotion() ?? false;
const [status, setStatus] = useState<Status>("offline");
const [elapsed, setElapsed] = useState(0);
const [attempts, setAttempts] = useState(0);
const [autoRetry, setAutoRetry] = useState(true);
const [countdown, setCountdown] = useState(AUTO_RETRY_SECONDS);
const [openStep, setOpenStep] = useState<number | null>(0);
const checkTimer = useRef<number | null>(null);
const statusRef = useRef<Status>(status);
const autoRef = useRef<boolean>(autoRetry);
const runCheckRef = useRef<() => void>(() => {});
useEffect(() => {
statusRef.current = status;
}, [status]);
useEffect(() => {
autoRef.current = autoRetry;
}, [autoRetry]);
const runCheck = useCallback(() => {
if (statusRef.current === "checking") return;
setStatus("checking");
setAttempts((a) => a + 1);
if (checkTimer.current !== null) window.clearTimeout(checkTimer.current);
checkTimer.current = window.setTimeout(
() => {
const online = typeof navigator !== "undefined" ? navigator.onLine : false;
setStatus(online ? "online" : "offline");
setCountdown(AUTO_RETRY_SECONDS);
checkTimer.current = null;
},
reduce ? 550 : 1700,
);
}, [reduce]);
useEffect(() => {
runCheckRef.current = runCheck;
});
// Real connectivity transitions from the browser.
useEffect(() => {
const goOnline = () => setStatus("online");
const goOffline = () => {
setStatus("offline");
setElapsed(0);
setCountdown(AUTO_RETRY_SECONDS);
};
window.addEventListener("online", goOnline);
window.addEventListener("offline", goOffline);
return () => {
window.removeEventListener("online", goOnline);
window.removeEventListener("offline", goOffline);
};
}, []);
// One ticker: counts offline time and drives the auto‑retry countdown.
useEffect(() => {
if (status === "online") return;
const id = window.setInterval(() => {
setElapsed((e) => e + 1);
if (statusRef.current === "offline" && autoRef.current) {
setCountdown((c) => {
if (c <= 1) {
runCheckRef.current();
return AUTO_RETRY_SECONDS;
}
return c - 1;
});
}
}, 1000);
return () => window.clearInterval(id);
}, [status]);
useEffect(
() => () => {
if (checkTimer.current !== null) window.clearTimeout(checkTimer.current);
},
[],
);
const liveMessage =
status === "online"
? "Connection restored. You are back online."
: status === "checking"
? "Checking your connection."
: "You are offline. Meridian will keep trying to reconnect.";
const pill =
status === "online"
? { label: "Connected", dot: "bg-emerald-500", ring: "ring-emerald-500/30", text: "text-emerald-700 dark:text-emerald-300", bg: "bg-emerald-50 dark:bg-emerald-500/10" }
: status === "checking"
? { label: "Checking…", dot: "bg-amber-500", ring: "ring-amber-500/30", text: "text-amber-700 dark:text-amber-300", bg: "bg-amber-50 dark:bg-amber-500/10" }
: { label: "No connection", dot: "bg-rose-500", ring: "ring-rose-500/30", text: "text-rose-700 dark:text-rose-300", bg: "bg-rose-50 dark:bg-rose-500/10" };
const arcStroke =
status === "online"
? "stroke-emerald-500"
: status === "checking"
? "stroke-indigo-400"
: "stroke-rose-300 dark:stroke-rose-500/60";
const arcs = [
{ d: "M63.6 106.5 A20 20 0 0 1 96.4 106.5", delay: "0s" },
{ d: "M50.5 97.4 A36 36 0 0 1 109.5 97.4", delay: "0.15s" },
{ d: "M37.4 88.2 A52 52 0 0 1 122.6 88.2", delay: "0.3s" },
];
return (
<section className="relative w-full overflow-hidden bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-100">
<style>{CSS}</style>
{/* decorative atmosphere */}
<div aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden">
<div className="absolute -left-24 -top-16 h-72 w-72 rounded-full bg-indigo-400/20 blur-3xl dark:bg-indigo-600/20" />
<div className="absolute -bottom-16 -right-16 h-72 w-72 rounded-full bg-rose-300/25 blur-3xl dark:bg-rose-700/15" />
<div
className="absolute inset-0"
style={{
backgroundImage:
"radial-gradient(circle at center, rgba(100,116,139,0.16) 1px, transparent 1.4px)",
backgroundSize: "22px 22px",
WebkitMaskImage: "radial-gradient(ellipse at center, black 35%, transparent 78%)",
maskImage: "radial-gradient(ellipse at center, black 35%, transparent 78%)",
}}
/>
</div>
<p aria-live="polite" className="sr-only">
{liveMessage}
</p>
<div className="relative mx-auto w-full max-w-5xl px-5 py-16 sm:px-8 sm:py-24">
{/* top bar */}
<div className="mb-8 flex items-center justify-between gap-4">
<div className="flex items-center gap-2.5">
<span className="grid h-9 w-9 place-items-center rounded-xl bg-gradient-to-br from-indigo-500 to-violet-600 text-white shadow-lg shadow-indigo-500/25">
<IconBolt className="h-5 w-5" />
</span>
<span className="text-sm font-semibold tracking-tight">Meridian</span>
</div>
<div
className={`inline-flex items-center gap-2 rounded-full px-3 py-1.5 text-xs font-medium ring-1 ${pill.bg} ${pill.text} ${pill.ring}`}
>
<span className="relative flex h-2 w-2">
{status === "offline" && !reduce && (
<span
className="eof-anim absolute inline-flex h-full w-full rounded-full bg-rose-500/60"
style={{ animation: "eof-ping 1.8s cubic-bezier(0,0,0.2,1) infinite" }}
/>
)}
<span className={`relative inline-flex h-2 w-2 rounded-full ${pill.dot}`} />
</span>
{pill.label}
</div>
</div>
{/* hero card */}
<motion.div
initial={reduce ? false : { opacity: 0, y: 14 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, ease: "easeOut" }}
className="relative overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-xl shadow-slate-900/5 dark:border-slate-800 dark:bg-slate-900 dark:shadow-black/30"
>
<div className="grid items-center gap-8 p-7 sm:p-10 md:grid-cols-[minmax(0,230px)_1fr]">
{/* illustration */}
<div className="mx-auto w-full max-w-[230px]">
<div className="eof-float relative aspect-square w-full">
<svg viewBox="0 0 160 160" className="h-full w-full" role="img" aria-label={status === "online" ? "Connection restored" : "No network connection"}>
<circle cx="80" cy="80" r="70" className="fill-slate-100 dark:fill-slate-800/60" />
<circle cx="80" cy="80" r="70" className="fill-none stroke-slate-200 dark:stroke-slate-700/70" strokeWidth={1} />
{status === "online" ? (
<g style={{ animation: reduce ? undefined : "eof-pop 0.5s ease-out both", transformOrigin: "80px 80px" }} className="eof-anim">
<circle cx="80" cy="80" r="38" className="fill-emerald-500/12 stroke-emerald-500" strokeWidth={2} />
<path d="M62 81 74 93 99 66" className="fill-none stroke-emerald-500" strokeWidth={7} strokeLinecap="round" strokeLinejoin="round" />
</g>
) : (
<>
{arcs.map((arc) => (
<path
key={arc.d}
d={arc.d}
className={`eof-anim fill-none ${arcStroke}`}
strokeWidth={7}
strokeLinecap="round"
style={{
animation:
status === "checking" && !reduce
? "eof-scan 1.2s ease-in-out infinite"
: undefined,
animationDelay: arc.delay,
opacity: status === "offline" ? 0.85 : 1,
}}
/>
))}
<circle
cx="80"
cy="118"
r="6"
className={
status === "checking" ? "fill-indigo-400" : "fill-rose-400 dark:fill-rose-500"
}
/>
{status === "checking" && !reduce && (
<circle
cx="80"
cy="118"
r="6"
className="eof-anim fill-none stroke-indigo-400"
strokeWidth={2}
style={{ animation: "eof-ping 1.4s ease-out infinite", transformOrigin: "80px 118px" }}
/>
)}
{status === "offline" && (
<line
x1="46"
y1="46"
x2="114"
y2="114"
className="eof-anim stroke-rose-500"
strokeWidth={7}
strokeLinecap="round"
strokeDasharray="120"
style={{ animation: reduce ? undefined : "eof-draw 0.55s ease-out both" }}
/>
)}
</>
)}
</svg>
</div>
</div>
{/* copy + actions */}
<div className="min-w-0">
{status === "online" ? (
<>
<h1 className="text-2xl font-bold tracking-tight sm:text-3xl">You’re back online</h1>
<p className="mt-3 max-w-prose text-sm leading-relaxed text-slate-600 dark:text-slate-400">
Connection restored after {fmt(elapsed)}. Meridian is syncing your queued
changes now — nothing was lost while you were away.
</p>
<div className="mt-6 flex flex-wrap items-center gap-3">
<button
type="button"
onClick={() => window.location.reload()}
className="inline-flex items-center justify-center gap-2 rounded-xl bg-gradient-to-br from-emerald-500 to-emerald-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-emerald-500/25 transition hover:from-emerald-400 hover:to-emerald-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-900"
>
Reload Meridian
<IconArrow className="h-4 w-4" />
</button>
</div>
</>
) : (
<>
<h1 className="text-2xl font-bold tracking-tight sm:text-3xl">You’re offline</h1>
<p className="mt-3 max-w-prose text-sm leading-relaxed text-slate-600 dark:text-slate-400">
Meridian can’t reach the server right now. Your work is saved locally and
will sync automatically the moment your connection returns.
</p>
<div className="mt-6 flex flex-wrap items-center gap-3">
<button
type="button"
onClick={runCheck}
disabled={status === "checking"}
className="group inline-flex items-center justify-center gap-2 rounded-xl bg-gradient-to-br from-indigo-500 to-violet-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-500/25 transition hover:from-indigo-400 hover:to-violet-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-70 dark:focus-visible:ring-offset-slate-900"
>
<IconRefresh
className={`h-4 w-4 ${
status === "checking" && !reduce ? "eof-anim" : ""
}`}
{...(status === "checking" && !reduce
? { }
: {})}
/>
{status === "checking" ? "Checking connection…" : "Try to reconnect"}
</button>
<span className="text-xs text-slate-500 dark:text-slate-400" aria-live="polite">
{status === "checking"
? "Reaching the server…"
: autoRetry
? `Next auto attempt in ${countdown}s`
: "Auto‑retry is off"}
</span>
</div>
{/* auto retry switch */}
<div className="mt-6 flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50/70 px-4 py-3 dark:border-slate-800 dark:bg-slate-800/40">
<button
type="button"
role="switch"
aria-checked={autoRetry}
onClick={() => setAutoRetry((v) => !v)}
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 dark:focus-visible:ring-offset-slate-900 ${
autoRetry ? "bg-indigo-500" : "bg-slate-300 dark:bg-slate-700"
}`}
>
<span className="sr-only">Retry automatically</span>
<span
className={`inline-block h-5 w-5 transform rounded-full bg-white shadow transition-transform ${
autoRetry ? "translate-x-5" : "translate-x-0.5"
}`}
/>
</button>
<div className="min-w-0">
<p className="text-sm font-medium">Retry automatically</p>
<p className="text-xs text-slate-500 dark:text-slate-400">
Reconnect on its own every {AUTO_RETRY_SECONDS} seconds.
</p>
</div>
</div>
</>
)}
</div>
</div>
{/* stat strip */}
<div className="grid grid-cols-3 divide-x divide-slate-200 border-t border-slate-200 dark:divide-slate-800 dark:border-slate-800">
<div className="flex flex-col gap-1 px-4 py-4 sm:px-8">
<span className="text-[11px] font-medium uppercase tracking-wide text-slate-400 dark:text-slate-500">
{status === "online" ? "Downtime" : "Offline for"}
</span>
<span className="inline-flex items-center gap-1.5 font-mono text-lg font-semibold tabular-nums">
<IconClock className="h-4 w-4 text-slate-400" />
{fmt(elapsed)}
</span>
</div>
<div className="flex flex-col gap-1 px-4 py-4 sm:px-8">
<span className="text-[11px] font-medium uppercase tracking-wide text-slate-400 dark:text-slate-500">
Attempts
</span>
<span className="font-mono text-lg font-semibold tabular-nums">{attempts}</span>
</div>
<div className="flex flex-col gap-1 px-4 py-4 sm:px-8">
<span className="text-[11px] font-medium uppercase tracking-wide text-slate-400 dark:text-slate-500">
Signal
</span>
<span className="flex items-center gap-2 text-lg font-semibold">
<SignalBars status={status} reduce={reduce} />
<span className="text-sm text-slate-500 dark:text-slate-400">
{status === "online" ? "Strong" : status === "checking" ? "Scanning" : "None"}
</span>
</span>
</div>
</div>
</motion.div>
{/* supporting grid */}
<div className="mt-6 grid gap-6 md:grid-cols-3">
{/* troubleshooting */}
<motion.div
initial={reduce ? false : { opacity: 0, y: 14 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, ease: "easeOut", delay: reduce ? 0 : 0.08 }}
className="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-800 dark:bg-slate-900 md:col-span-2"
>
<h2 className="text-base font-semibold tracking-tight">Try these fixes</h2>
<p className="mt-1 text-sm text-slate-500 dark:text-slate-400">
Most connection drops clear up within a step or two.
</p>
<div className="mt-4 divide-y divide-slate-200 dark:divide-slate-800">
{TROUBLESHOOT.map((step, i) => {
const open = openStep === i;
return (
<div key={step.title} className="py-1">
<h3>
<button
type="button"
aria-expanded={open}
aria-controls={`eof-panel-${i}`}
id={`eof-tab-${i}`}
onClick={() => setOpenStep(open ? null : i)}
className="flex w-full items-center gap-3 rounded-lg px-2 py-3 text-left transition hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:hover:bg-slate-800/60"
>
<span className="grid h-7 w-7 shrink-0 place-items-center rounded-full bg-indigo-50 text-xs font-semibold text-indigo-600 dark:bg-indigo-500/15 dark:text-indigo-300">
{i + 1}
</span>
<span className="min-w-0 flex-1 text-sm font-medium">{step.title}</span>
<IconChevron
className={`h-4 w-4 shrink-0 text-slate-400 transition-transform duration-200 ${
open ? "rotate-180" : ""
}`}
/>
</button>
</h3>
<div
id={`eof-panel-${i}`}
role="region"
aria-labelledby={`eof-tab-${i}`}
hidden={!open}
className="px-2 pb-3 pl-12 text-sm leading-relaxed text-slate-600 dark:text-slate-400"
>
{step.detail}
</div>
</div>
);
})}
</div>
</motion.div>
{/* side column */}
<motion.div
initial={reduce ? false : { opacity: 0, y: 14 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, ease: "easeOut", delay: reduce ? 0 : 0.16 }}
className="flex flex-col gap-6"
>
{/* queued to sync */}
<div className="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-800 dark:bg-slate-900">
<div className="flex items-center justify-between">
<h2 className="text-base font-semibold tracking-tight">Waiting to sync</h2>
<span className="inline-flex items-center gap-1.5 rounded-full bg-amber-50 px-2.5 py-1 text-xs font-semibold text-amber-700 ring-1 ring-amber-500/20 dark:bg-amber-500/10 dark:text-amber-300">
<IconClock className="h-3.5 w-3.5" />
{QUEUED.length}
</span>
</div>
<ul className="mt-4 space-y-3">
{QUEUED.map((item) => (
<li key={item.title} className="flex items-start gap-3">
<span
className="mt-1.5 h-2 w-2 shrink-0 rounded-full bg-amber-400"
aria-hidden="true"
/>
<div className="min-w-0">
<p className="truncate text-sm font-medium">{item.title}</p>
<p className="text-xs text-slate-500 dark:text-slate-400">
<span className="font-medium text-slate-400 dark:text-slate-500">
{item.kind}
</span>{" "}
· {item.meta}
</p>
</div>
</li>
))}
</ul>
<p className="mt-4 text-xs text-slate-500 dark:text-slate-400">
These will upload automatically once you reconnect.
</p>
</div>
{/* available offline */}
<div className="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-800 dark:bg-slate-900">
<h2 className="text-base font-semibold tracking-tight">Still available offline</h2>
<ul className="mt-4 space-y-1.5">
{CACHED.map((c) => (
<li key={c.name}>
<button
type="button"
className="flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-left transition hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:hover:bg-slate-800/60"
>
<span className="grid h-8 w-8 shrink-0 place-items-center rounded-lg bg-sky-50 text-sky-600 dark:bg-sky-500/15 dark:text-sky-300">
<IconArrow className="h-4 w-4" />
</span>
<span className="min-w-0 flex-1">
<span className="block truncate text-sm font-medium">{c.name}</span>
<span className="block text-xs text-slate-500 dark:text-slate-400">
{c.note}
</span>
</span>
<span className="shrink-0 rounded-md bg-slate-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-slate-500 dark:bg-slate-800 dark:text-slate-400">
Cached
</span>
</button>
</li>
))}
</ul>
</div>
</motion.div>
</div>
<p className="mt-8 text-center text-xs text-slate-400 dark:text-slate-600">
Error code <span className="font-mono">NET::ERR_INTERNET_DISCONNECTED</span> · Meridian
works offline
</p>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Error 404
Originalfriendly 404 page with illustration and CTA

Error 500
Original500 server error page

Error 403
Original403 access denied page

Error Maintenance
Originalscheduled maintenance page

Error Coming Soon
Originalcoming soon page with countdown and email

Error Under Construction
Originalunder construction page

Error Search Empty
Originalno search results page

Error Expired
Originallink/session expired page

Error Generic
Originalgeneric error page with retry

Spotlight Hero
OriginalA centred hero with a soft radial spotlight, badge and dual call-to-action.

Split Hero
OriginalA two-column hero pairing a headline and CTAs with a product mock and social proof.

Gradient Spotlight Hero
OriginalA minimal centred hero with a soft gradient-mesh backdrop, announcement pill and dual call-to-action buttons.

