Empty Error
Original · freesomething went wrong empty 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/empty-error.json"use client";
import { useCallback, useEffect, useId, useRef, useState } from "react";
import { motion, useReducedMotion } from "motion/react";
type Phase = "failed" | "retrying" | "recovered";
type CheckState = "ok" | "failed" | "checking";
type Check = {
id: string;
label: string;
okDetail: string;
failDetail: string;
alwaysOk: boolean;
};
const CHECKS: Check[] = [
{
id: "network",
label: "Your connection",
okDetail: "Reachable — 38 ms round trip to the edge in Mumbai.",
failDetail: "Reachable — 38 ms round trip to the edge in Mumbai.",
alwaysOk: true,
},
{
id: "auth",
label: "Session & permissions",
okDetail: "Signed in as salman@webinnoventix.com. Token valid for 6 more days.",
failDetail: "Signed in as salman@webinnoventix.com. Token valid for 6 more days.",
alwaysOk: true,
},
{
id: "api",
label: "Metrics API (us-east-1)",
okDetail: "Responding in 210 ms. Aggregation pipeline caught up at 14:04 UTC.",
failDetail: "Returned 503 after 30 s. The warehouse read timed out mid-aggregation.",
alwaysOk: false,
},
];
const MAX_ATTEMPTS = 3;
const COOLDOWNS = [6, 10];
const DIAGNOSTIC_REPORT = [
"Error: 503 Service Unavailable",
"Endpoint: GET /v2/metrics/summary?range=30d",
"Request ID: req_8f3c21ad9b40e7",
"Region: us-east-1 (replica 3 of 4)",
"Time: 2026-07-17 14:02:19 UTC",
"Trace: pipeline.aggregate -> warehouse.read (timed out after 30000 ms)",
].join("\n");
function IconAlert({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
<path
d="M12 4.2 21 19.5H3L12 4.2Z"
stroke="currentColor"
strokeWidth="1.7"
strokeLinejoin="round"
/>
<path d="M12 10v4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
<circle cx="12" cy="17" r="1.05" fill="currentColor" />
</svg>
);
}
function IconCheck({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
<path
d="M5 12.5 10 17.5 19 7"
stroke="currentColor"
strokeWidth="2.4"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
function IconCross({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
<path d="M6.5 6.5 17.5 17.5M17.5 6.5 6.5 17.5" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" />
</svg>
);
}
function IconSpinner({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
<circle cx="12" cy="12" r="8" stroke="currentColor" strokeOpacity="0.25" strokeWidth="2.4" />
<path d="M20 12a8 8 0 0 0-8-8" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" />
</svg>
);
}
function IconRefresh({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
<path
d="M20 12a8 8 0 1 1-2.6-5.9"
stroke="currentColor"
strokeWidth="1.9"
strokeLinecap="round"
/>
<path d="M20 4v4.4h-4.4" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
function IconChevron({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
<path d="M7 10l5 5 5-5" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
function IconCopy({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
<rect x="9" y="9" width="11" height="11" rx="2.4" stroke="currentColor" strokeWidth="1.7" />
<path
d="M15 6.2V5.6A1.6 1.6 0 0 0 13.4 4H5.6A1.6 1.6 0 0 0 4 5.6v7.8A1.6 1.6 0 0 0 5.6 15h.6"
stroke="currentColor"
strokeWidth="1.7"
strokeLinecap="round"
/>
</svg>
);
}
function StatusDot({ state }: { state: CheckState }) {
if (state === "checking") {
return (
<span className="grid h-6 w-6 shrink-0 place-items-center rounded-full bg-amber-100 text-amber-700 dark:bg-amber-400/15 dark:text-amber-300">
<IconSpinner className="eerr-spin h-3.5 w-3.5" />
</span>
);
}
if (state === "failed") {
return (
<span className="grid h-6 w-6 shrink-0 place-items-center rounded-full bg-rose-100 text-rose-700 dark:bg-rose-500/15 dark:text-rose-300">
<IconCross className="h-3.5 w-3.5" />
</span>
);
}
return (
<span className="grid h-6 w-6 shrink-0 place-items-center rounded-full bg-emerald-100 text-emerald-700 dark:bg-emerald-400/15 dark:text-emerald-300">
<IconCheck className="h-3.5 w-3.5" />
</span>
);
}
export default function EmptyError() {
const reduced = useReducedMotion();
const headingId = useId();
const detailsId = useId();
const statusId = useId();
const [phase, setPhase] = useState<Phase>("failed");
const [attempts, setAttempts] = useState(0);
const [cooldown, setCooldown] = useState(0);
const [detailsOpen, setDetailsOpen] = useState(false);
const [copied, setCopied] = useState(false);
const retryTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const copyTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const reportRef = useRef<HTMLPreElement | null>(null);
useEffect(() => {
return () => {
if (retryTimer.current) clearTimeout(retryTimer.current);
if (copyTimer.current) clearTimeout(copyTimer.current);
};
}, []);
useEffect(() => {
if (cooldown <= 0) return;
const id = setTimeout(() => setCooldown((value) => Math.max(0, value - 1)), 1000);
return () => clearTimeout(id);
}, [cooldown]);
const runRetry = useCallback(() => {
if (phase === "retrying" || cooldown > 0) return;
setPhase("retrying");
retryTimer.current = setTimeout(() => {
setAttempts((previous) => {
const next = previous + 1;
if (next >= MAX_ATTEMPTS) {
setPhase("recovered");
} else {
setPhase("failed");
setCooldown(COOLDOWNS[Math.min(next - 1, COOLDOWNS.length - 1)]);
}
return next;
});
}, 1500);
}, [cooldown, phase]);
const resetDemo = useCallback(() => {
if (retryTimer.current) clearTimeout(retryTimer.current);
setPhase("failed");
setAttempts(0);
setCooldown(0);
}, []);
const copyReport = useCallback(async () => {
let done = false;
try {
if (typeof navigator !== "undefined" && navigator.clipboard) {
await navigator.clipboard.writeText(DIAGNOSTIC_REPORT);
done = true;
}
} catch {
done = false;
}
if (!done && reportRef.current && typeof window !== "undefined") {
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(reportRef.current);
selection?.removeAllRanges();
selection?.addRange(range);
}
setCopied(true);
if (copyTimer.current) clearTimeout(copyTimer.current);
copyTimer.current = setTimeout(() => setCopied(false), 2200);
}, []);
const apiState: CheckState =
phase === "retrying" ? "checking" : phase === "recovered" ? "ok" : "failed";
const retryDisabled = phase === "retrying" || cooldown > 0;
const statusMessage =
phase === "retrying"
? `Retrying request, attempt ${attempts + 1} of ${MAX_ATTEMPTS}.`
: phase === "recovered"
? "The metrics API responded. Your dashboard is ready to load."
: attempts === 0
? "The metrics request failed with a 503 error."
: `Attempt ${attempts} failed. Waiting ${cooldown} seconds before the next try.`;
return (
<section
aria-labelledby={headingId}
className="relative w-full overflow-hidden bg-zinc-50 px-5 py-20 sm:px-8 sm:py-28 dark:bg-zinc-950"
>
<style>{`
@keyframes eerr-jitter {
0%, 88%, 100% { transform: translate3d(0, 0, 0); }
90% { transform: translate3d(-2px, 1px, 0); }
93% { transform: translate3d(2px, -1px, 0); }
96% { transform: translate3d(-1px, 0, 0); }
}
@keyframes eerr-scan {
0% { transform: translateY(-130%); opacity: 0; }
45% { opacity: 1; }
100% { transform: translateY(130%); opacity: 0; }
}
@keyframes eerr-halo {
0% { transform: scale(0.9); opacity: 0.5; }
100% { transform: scale(1.55); opacity: 0; }
}
@keyframes eerr-spin {
to { transform: rotate(360deg); }
}
.eerr-jitter { animation: eerr-jitter 5s steps(1, end) infinite; }
.eerr-scan { animation: eerr-scan 3.2s cubic-bezier(0.45, 0, 0.2, 1) infinite; }
.eerr-halo { animation: eerr-halo 2.6s ease-out infinite; }
.eerr-spin { animation: eerr-spin 0.9s linear infinite; transform-origin: 50% 50%; }
@media (prefers-reduced-motion: reduce) {
.eerr-jitter, .eerr-scan, .eerr-halo, .eerr-spin {
animation: none !important;
}
}
`}</style>
<div
aria-hidden="true"
className="pointer-events-none absolute inset-x-0 top-0 h-80 bg-[radial-gradient(65%_100%_at_50%_0%,rgba(244,63,94,0.13),transparent_72%)] dark:bg-[radial-gradient(65%_100%_at_50%_0%,rgba(244,63,94,0.18),transparent_72%)]"
/>
<div
aria-hidden="true"
className="pointer-events-none absolute inset-0 opacity-[0.35] [background-image:linear-gradient(to_right,rgba(100,116,139,0.12)_1px,transparent_1px),linear-gradient(to_bottom,rgba(100,116,139,0.12)_1px,transparent_1px)] [background-size:56px_56px] [mask-image:radial-gradient(70%_60%_at_50%_20%,black,transparent)] dark:opacity-[0.25]"
/>
<div className="relative mx-auto w-full max-w-2xl">
<div className="rounded-3xl border border-zinc-200/80 bg-white/85 p-6 shadow-[0_1px_2px_rgba(9,9,11,0.05),0_28px_70px_-32px_rgba(9,9,11,0.3)] backdrop-blur-sm sm:p-10 dark:border-zinc-800 dark:bg-zinc-900/75 dark:shadow-[0_28px_70px_-32px_rgba(0,0,0,0.9)]">
<div className="flex flex-col items-center text-center">
<div className="relative mb-7 grid h-24 w-24 place-items-center">
{phase !== "recovered" ? (
<span
aria-hidden="true"
className="eerr-halo absolute inset-2 rounded-2xl border border-rose-400/50 dark:border-rose-400/40"
/>
) : null}
<motion.div
key={phase === "recovered" ? "ok" : "bad"}
initial={reduced ? false : { scale: 0.82, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ type: "spring", stiffness: 280, damping: 19 }}
className={`relative grid h-20 w-20 place-items-center overflow-hidden rounded-2xl text-white shadow-lg ${
phase === "recovered"
? "bg-emerald-500 shadow-emerald-500/25 dark:bg-emerald-400 dark:text-emerald-950 dark:shadow-emerald-400/20"
: "eerr-jitter bg-rose-500 shadow-rose-500/25 dark:bg-rose-500 dark:shadow-rose-500/25"
}`}
>
{phase === "recovered" ? (
<IconCheck className="h-9 w-9" />
) : (
<IconAlert className="h-9 w-9" />
)}
<span
aria-hidden="true"
className="eerr-scan absolute inset-x-0 -top-1/3 h-1/3 bg-white/25"
/>
</motion.div>
</div>
<p className="mb-3 inline-flex items-center gap-2 rounded-full border border-zinc-200 bg-zinc-100/80 px-3 py-1 font-mono text-[11px] font-medium tracking-wide text-zinc-600 dark:border-zinc-800 dark:bg-zinc-950/60 dark:text-zinc-400">
<span
aria-hidden="true"
className={`h-1.5 w-1.5 rounded-full ${
phase === "recovered" ? "bg-emerald-500" : "bg-rose-500"
}`}
/>
503 · req_8f3c21ad9b40e7
</p>
<h2
id={headingId}
className="text-balance text-3xl font-semibold tracking-tight text-zinc-900 sm:text-4xl dark:text-zinc-50"
>
{phase === "recovered"
? "Back online. Nothing was lost."
: "We couldn't load your metrics."}
</h2>
<p className="mt-3 max-w-md text-pretty text-base leading-relaxed text-zinc-600 dark:text-zinc-400">
{phase === "recovered"
? "The warehouse replica finished catching up and returned all 30 days of data. Your filters and date range were kept exactly as you left them."
: "The request timed out while our warehouse replica was catching up. This is on us, not on you — your data is safe and nothing was deleted."}
</p>
<p
id={statusId}
role="status"
aria-live="polite"
className="mt-3 min-h-5 text-sm font-medium text-zinc-500 dark:text-zinc-400"
>
{statusMessage}
</p>
</div>
<div className="mt-8 flex flex-col gap-3 sm:flex-row sm:justify-center">
{phase === "recovered" ? (
<>
<a
href="#dashboard"
className="inline-flex items-center justify-center gap-2 rounded-xl bg-emerald-600 px-5 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-emerald-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:bg-emerald-500 dark:hover:bg-emerald-400 dark:focus-visible:ring-emerald-400 dark:focus-visible:ring-offset-zinc-900"
>
Open the dashboard
</a>
<button
type="button"
onClick={resetDemo}
className="inline-flex items-center justify-center gap-2 rounded-xl border border-zinc-300 px-5 py-2.5 text-sm font-semibold text-zinc-700 transition-colors hover:bg-zinc-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-zinc-700 dark:text-zinc-200 dark:hover:bg-zinc-800 dark:focus-visible:ring-rose-400 dark:focus-visible:ring-offset-zinc-900"
>
Replay the failure
</button>
</>
) : (
<>
<button
type="button"
onClick={runRetry}
disabled={retryDisabled}
aria-describedby={statusId}
className="inline-flex items-center justify-center gap-2 rounded-xl bg-zinc-900 px-5 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-zinc-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-55 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-white dark:focus-visible:ring-rose-400 dark:focus-visible:ring-offset-zinc-900"
>
{phase === "retrying" ? (
<IconSpinner className="eerr-spin h-4 w-4" />
) : (
<IconRefresh className="h-4 w-4" />
)}
{phase === "retrying"
? "Retrying…"
: cooldown > 0
? `Retry in ${cooldown}s`
: attempts === 0
? "Try again"
: `Try again (attempt ${attempts + 1})`}
</button>
<a
href="#status"
className="inline-flex items-center justify-center gap-2 rounded-xl border border-zinc-300 px-5 py-2.5 text-sm font-semibold text-zinc-700 transition-colors hover:bg-zinc-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-zinc-700 dark:text-zinc-200 dark:hover:bg-zinc-800 dark:focus-visible:ring-rose-400 dark:focus-visible:ring-offset-zinc-900"
>
Check the status page
</a>
</>
)}
</div>
<ul className="mt-9 space-y-2.5">
{CHECKS.map((check) => {
const state: CheckState = check.alwaysOk ? "ok" : apiState;
return (
<li
key={check.id}
className="flex items-start gap-3 rounded-2xl border border-zinc-200 bg-white px-4 py-3.5 text-left dark:border-zinc-800 dark:bg-zinc-900"
>
<span className="mt-0.5">
<StatusDot state={state} />
</span>
<span className="min-w-0 flex-1">
<span className="block text-sm font-medium text-zinc-900 dark:text-zinc-100">
{check.label}
</span>
<motion.span
key={`${check.id}-${state}`}
initial={reduced ? false : { opacity: 0, y: 4 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.24, ease: "easeOut" }}
className="mt-0.5 block text-pretty text-sm leading-relaxed text-zinc-500 dark:text-zinc-400"
>
{state === "checking"
? "Re-running the request against replica 3…"
: state === "failed"
? check.failDetail
: check.okDetail}
</motion.span>
</span>
</li>
);
})}
</ul>
<div className="mt-6 overflow-hidden rounded-2xl border border-zinc-200 dark:border-zinc-800">
<h3>
<button
type="button"
onClick={() => setDetailsOpen((open) => !open)}
aria-expanded={detailsOpen}
aria-controls={detailsId}
className="flex w-full items-center justify-between gap-3 bg-zinc-50 px-4 py-3 text-left text-sm font-medium text-zinc-700 transition-colors hover:bg-zinc-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-rose-500 dark:bg-zinc-950/50 dark:text-zinc-300 dark:hover:bg-zinc-950 dark:focus-visible:ring-rose-400"
>
<span>Technical details for support</span>
<IconChevron
className={`h-4 w-4 shrink-0 transition-transform duration-200 ${
detailsOpen ? "rotate-180" : ""
}`}
/>
</button>
</h3>
<div id={detailsId} hidden={!detailsOpen} className="bg-white dark:bg-zinc-900">
<div className="space-y-3 px-4 py-4">
<pre
ref={reportRef}
className="overflow-x-auto rounded-xl bg-zinc-900 p-4 font-mono text-[12px] leading-relaxed text-zinc-200 dark:bg-zinc-950 dark:text-zinc-300"
>
{DIAGNOSTIC_REPORT}
</pre>
<div className="flex flex-wrap items-center gap-3">
<button
type="button"
onClick={copyReport}
className="inline-flex items-center gap-2 rounded-lg border border-zinc-300 px-3 py-1.5 text-xs font-semibold text-zinc-700 transition-colors hover:bg-zinc-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-zinc-700 dark:text-zinc-200 dark:hover:bg-zinc-800 dark:focus-visible:ring-rose-400 dark:focus-visible:ring-offset-zinc-900"
>
{copied ? <IconCheck className="h-3.5 w-3.5" /> : <IconCopy className="h-3.5 w-3.5" />}
{copied ? "Copied to clipboard" : "Copy report"}
</button>
<span role="status" aria-live="polite" className="sr-only">
{copied ? "Diagnostic report copied to clipboard." : ""}
</span>
<a
href="#support"
className="rounded-md text-xs font-medium text-rose-600 underline-offset-4 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:text-rose-400 dark:focus-visible:ring-rose-400 dark:focus-visible:ring-offset-zinc-900"
>
Send this to a human
</a>
</div>
</div>
</div>
</div>
</div>
<p className="mt-5 text-center text-xs text-zinc-500 dark:text-zinc-500">
Incident INC-2291 opened automatically · 3 of 4 replicas healthy · Updated every 30 seconds
</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 →
Empty No Data
Originalempty data state with illustration

Empty No Results
Originalno results with clear filters

Empty Cart
Originalempty shopping cart state

Empty Inbox
Originalempty inbox / all caught up

Empty First Run
Originalfirst-run get started empty state

Empty Notifications
Originalno notifications state

Empty No Connection
Originalno connection empty state

Empty Folder
Originalempty folder with upload CTA

Empty Success
Originalsuccess / done empty state

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.

