Error Under Construction
Original · freeunder construction page
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-under-construction.json"use client";
import { useCallback, useId, useRef, useState, type KeyboardEvent } from "react";
import { motion, useReducedMotion } from "motion/react";
type PhaseStatus = "complete" | "active" | "queued";
interface Phase {
id: string;
label: string;
status: PhaseStatus;
percent: number;
headline: string;
note: string;
items: readonly string[];
}
const PHASES: readonly Phase[] = [
{
id: "foundations",
label: "Foundations",
status: "complete",
percent: 100,
headline: "Signed off 14 July",
note: "Routing, the component data model and the auth shell all landed. 24 of 24 checks green on the last run.",
items: [
"URL scheme locked to /playground/[slug]",
"Registry schema migrated, 831 entries validated",
"Session + rate limiting behind the preview route",
],
},
{
id: "framing",
label: "Framing",
status: "active",
percent: 64,
headline: "In progress — 9 of 14 tickets merged",
note: "The layout shell, prop controls and live preview pane are going up now. This is the loud part of the build.",
items: [
"Split-pane preview with resizable canvas — merged",
"Prop controls for booleans, enums and strings — merged",
"Theme + viewport switcher — in review",
"Error boundary around user-edited props — open",
],
},
{
id: "fit-out",
label: "Fit-out",
status: "queued",
percent: 0,
headline: "Starts once framing is signed off",
note: "Docs, keyboard shortcuts, shareable snapshot links and the copy-to-clipboard flow. Nothing here is started yet.",
items: [
"Per-component usage docs pulled from source",
"Cmd+K palette and shortcut sheet",
"Share a playground state as a short link",
],
},
];
const OVERALL_PERCENT = 62;
function HardHatIcon() {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4"
aria-hidden="true"
focusable="false"
>
<path d="M4 16a8 8 0 0 1 16 0" />
<path d="M10 16V6.5A1.5 1.5 0 0 1 11.5 5h1A1.5 1.5 0 0 1 14 6.5V16" />
<path d="M2.5 16h19" />
<path d="M3.5 19h17" />
</svg>
);
}
function CheckIcon() {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.4"
strokeLinecap="round"
strokeLinejoin="round"
className="h-3 w-3"
aria-hidden="true"
focusable="false"
>
<path d="m4 12.5 5 5L20 6.5" />
</svg>
);
}
function WrenchIcon() {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-3 w-3"
aria-hidden="true"
focusable="false"
>
<path d="M15.5 3.5a5 5 0 0 0-6.2 6.2L3.7 15.3a2 2 0 0 0 0 2.8l2.2 2.2a2 2 0 0 0 2.8 0l5.6-5.6a5 5 0 0 0 6.2-6.2l-3.1 3.1-2.8-.4-.4-2.8Z" />
</svg>
);
}
function ClockIcon() {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-3 w-3"
aria-hidden="true"
focusable="false"
>
<circle cx="12" cy="12" r="9" />
<path d="M12 7.5V12l3 2" />
</svg>
);
}
function ArrowLeftIcon() {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4"
aria-hidden="true"
focusable="false"
>
<path d="M19 12H5" />
<path d="m11 6-6 6 6 6" />
</svg>
);
}
function ArrowRightIcon() {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4"
aria-hidden="true"
focusable="false"
>
<path d="M5 12h14" />
<path d="m13 6 6 6-6 6" />
</svg>
);
}
function StatusChip({ status }: { status: PhaseStatus }) {
if (status === "complete") {
return (
<span className="inline-flex items-center gap-1.5 rounded-full bg-emerald-500/10 px-2.5 py-1 text-[11px] font-semibold uppercase tracking-wider text-emerald-700 ring-1 ring-inset ring-emerald-600/25 dark:text-emerald-300 dark:ring-emerald-400/25">
<CheckIcon />
Complete
</span>
);
}
if (status === "active") {
return (
<span className="inline-flex items-center gap-1.5 rounded-full bg-amber-500/10 px-2.5 py-1 text-[11px] font-semibold uppercase tracking-wider text-amber-700 ring-1 ring-inset ring-amber-600/30 dark:text-amber-300 dark:ring-amber-400/30">
<WrenchIcon />
Building now
</span>
);
}
return (
<span className="inline-flex items-center gap-1.5 rounded-full bg-zinc-500/10 px-2.5 py-1 text-[11px] font-semibold uppercase tracking-wider text-zinc-600 ring-1 ring-inset ring-zinc-500/25 dark:text-zinc-400 dark:ring-zinc-400/20">
<ClockIcon />
Queued
</span>
);
}
export default function ErrorUnderConstruction() {
const reduceMotion = useReducedMotion();
const baseId = useId();
const [activeIndex, setActiveIndex] = useState<number>(1);
const [blueprint, setBlueprint] = useState<boolean>(false);
const tabRefs = useRef<Array<HTMLButtonElement | null>>([]);
const activePhase = PHASES[activeIndex];
const focusTab = useCallback((index: number) => {
setActiveIndex(index);
tabRefs.current[index]?.focus();
}, []);
const onTabKeyDown = useCallback(
(event: KeyboardEvent<HTMLButtonElement>) => {
const last = PHASES.length - 1;
if (event.key === "ArrowRight" || event.key === "ArrowDown") {
event.preventDefault();
focusTab(activeIndex === last ? 0 : activeIndex + 1);
} else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
event.preventDefault();
focusTab(activeIndex === 0 ? last : activeIndex - 1);
} else if (event.key === "Home") {
event.preventDefault();
focusTab(0);
} else if (event.key === "End") {
event.preventDefault();
focusTab(last);
}
},
[activeIndex, focusTab],
);
return (
<section className="relative w-full overflow-hidden bg-white px-5 py-20 text-zinc-900 sm:px-8 sm:py-28 dark:bg-zinc-950 dark:text-zinc-50">
<style>{`
@keyframes euc-stripe-drift {
from { background-position: 0 0; }
to { background-position: 64px 0; }
}
@keyframes euc-sweep {
0% { transform: translateY(-110%); opacity: 0; }
22% { opacity: 1; }
78% { opacity: 1; }
100% { transform: translateY(560%); opacity: 0; }
}
@keyframes euc-beacon {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.35; transform: scale(0.72); }
}
.euc-stripes {
background-image: repeating-linear-gradient(
135deg,
currentColor 0 12px,
transparent 12px 32px
);
background-size: 64px 64px;
animation: euc-stripe-drift 2.6s linear infinite;
}
.euc-grid {
background-image:
linear-gradient(to right, currentColor 1px, transparent 1px),
linear-gradient(to bottom, currentColor 1px, transparent 1px);
background-size: 28px 28px;
}
.euc-sweep {
animation: euc-sweep 6.5s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
.euc-beacon {
animation: euc-beacon 1.5s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.euc-stripes,
.euc-sweep,
.euc-beacon {
animation: none !important;
}
.euc-sweep {
opacity: 0;
}
}
`}</style>
<div
aria-hidden="true"
className={`pointer-events-none absolute inset-0 text-sky-500/[0.16] transition-opacity duration-500 dark:text-sky-300/[0.14] ${
blueprint ? "euc-grid opacity-100" : "opacity-0"
}`}
/>
<div
aria-hidden="true"
className="pointer-events-none absolute -top-40 left-1/2 h-80 w-[42rem] -translate-x-1/2 rounded-full bg-amber-300/25 blur-3xl dark:bg-amber-500/10"
/>
<div className="relative mx-auto max-w-6xl">
<div
aria-hidden="true"
className="euc-stripes h-2.5 w-full rounded-full text-amber-400 dark:text-amber-300/80"
/>
<div className="mt-12 grid grid-cols-1 gap-12 lg:mt-16 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.05fr)] lg:gap-16">
<div>
<p className="inline-flex items-center gap-2 rounded-full bg-amber-500/10 px-3 py-1.5 text-xs font-semibold uppercase tracking-[0.14em] text-amber-800 ring-1 ring-inset ring-amber-600/25 dark:text-amber-200 dark:ring-amber-400/25">
<HardHatIcon />
Zone C · Crew on site
</p>
<h1 className="mt-6 text-4xl font-semibold leading-[1.08] tracking-tight text-zinc-900 sm:text-5xl lg:text-6xl dark:text-white">
This page is still
<span className="block text-amber-600 dark:text-amber-400">
a building site.
</span>
</h1>
<p className="mt-6 max-w-xl text-base leading-relaxed text-zinc-600 sm:text-lg dark:text-zinc-400">
The component playground is going up right now. Foundations are
poured and the framing is half up — but the doors aren't on
yet, so we're keeping visitors behind the tape. Everything
else on the site is open and running as normal.
</p>
<div className="mt-10 rounded-2xl border border-zinc-200 bg-zinc-50/70 p-5 dark:border-zinc-800 dark:bg-zinc-900/60">
<div className="flex items-baseline justify-between gap-4">
<span className="text-sm font-medium text-zinc-700 dark:text-zinc-300">
Overall build progress
</span>
<span className="text-2xl font-semibold tabular-nums text-zinc-900 dark:text-white">
{OVERALL_PERCENT}
<span className="text-base text-zinc-500 dark:text-zinc-500">
%
</span>
</span>
</div>
<div
role="progressbar"
aria-valuenow={OVERALL_PERCENT}
aria-valuemin={0}
aria-valuemax={100}
aria-label="Overall build progress"
className="mt-3 h-2.5 w-full overflow-hidden rounded-full bg-zinc-200 dark:bg-zinc-800"
>
<motion.div
className="h-full rounded-full bg-amber-500 dark:bg-amber-400"
initial={reduceMotion ? false : { width: 0 }}
animate={{ width: `${OVERALL_PERCENT}%` }}
transition={
reduceMotion
? { duration: 0 }
: { duration: 1.1, ease: [0.22, 1, 0.36, 1], delay: 0.15 }
}
style={reduceMotion ? { width: `${OVERALL_PERCENT}%` } : undefined}
/>
</div>
<p className="mt-3 flex items-center gap-2 text-xs text-zinc-500 dark:text-zinc-500">
<span className="euc-beacon inline-block h-1.5 w-1.5 shrink-0 rounded-full bg-amber-500 dark:bg-amber-400" />
Handover targeted for Friday 8 August · last commit 40 minutes
ago
</p>
</div>
<div className="mt-8 flex flex-col gap-3 sm:flex-row sm:items-center">
<a
href="/"
className="inline-flex items-center justify-center gap-2 rounded-xl bg-zinc-900 px-5 py-3 text-sm font-semibold text-white transition-colors hover:bg-zinc-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:bg-white dark:text-zinc-900 dark:hover:bg-zinc-200 dark:focus-visible:ring-offset-zinc-950"
>
<ArrowLeftIcon />
Back to the homepage
</a>
<a
href="/changelog"
className="group inline-flex items-center justify-center gap-2 rounded-xl border border-zinc-300 px-5 py-3 text-sm font-semibold text-zinc-800 transition-colors hover:border-zinc-400 hover:bg-zinc-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-zinc-700 dark:text-zinc-200 dark:hover:border-zinc-600 dark:hover:bg-zinc-900 dark:focus-visible:ring-offset-zinc-950"
>
Read the build log
<span className="transition-transform group-hover:translate-x-0.5">
<ArrowRightIcon />
</span>
</a>
</div>
</div>
<div className="relative">
<div className="relative overflow-hidden rounded-3xl border border-zinc-200 bg-white p-6 shadow-sm sm:p-7 dark:border-zinc-800 dark:bg-zinc-900/70">
<div
aria-hidden="true"
className="euc-sweep pointer-events-none absolute inset-x-0 top-0 h-24 bg-gradient-to-b from-transparent via-sky-400/10 to-transparent dark:via-sky-300/10"
/>
<div className="relative flex flex-wrap items-center justify-between gap-4">
<div>
<h2 className="text-sm font-semibold text-zinc-900 dark:text-white">
Site plan
</h2>
<p className="mt-1 text-xs text-zinc-500 dark:text-zinc-500">
Inspect any phase of the build
</p>
</div>
<div className="flex items-center gap-2.5">
<label
htmlFor={`${baseId}-blueprint`}
className="text-xs font-medium text-zinc-600 dark:text-zinc-400"
>
Blueprint view
</label>
<button
id={`${baseId}-blueprint`}
type="button"
role="switch"
aria-checked={blueprint}
onClick={() => setBlueprint((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-amber-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-zinc-950 ${
blueprint
? "bg-sky-600 dark:bg-sky-500"
: "bg-zinc-300 dark:bg-zinc-700"
}`}
>
<span
aria-hidden="true"
className={`inline-block h-[1.125rem] w-[1.125rem] transform rounded-full bg-white shadow transition-transform ${
blueprint ? "translate-x-[1.4rem]" : "translate-x-[0.2rem]"
}`}
/>
</button>
</div>
</div>
<div
role="tablist"
aria-label="Build phases"
aria-orientation="horizontal"
className="relative mt-6 flex gap-1 rounded-xl bg-zinc-100 p-1 dark:bg-zinc-800/70"
>
{PHASES.map((phase, index) => {
const selected = index === activeIndex;
return (
<button
key={phase.id}
ref={(node) => {
tabRefs.current[index] = node;
}}
id={`${baseId}-tab-${phase.id}`}
type="button"
role="tab"
aria-selected={selected}
aria-controls={`${baseId}-panel-${phase.id}`}
tabIndex={selected ? 0 : -1}
onClick={() => setActiveIndex(index)}
onKeyDown={onTabKeyDown}
className={`relative flex-1 rounded-lg px-3 py-2 text-xs font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500 focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-100 sm:text-sm dark:focus-visible:ring-offset-zinc-800 ${
selected
? "text-zinc-900 dark:text-white"
: "text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200"
}`}
>
{selected ? (
<motion.span
layoutId={`${baseId}-tab-pill`}
className="absolute inset-0 rounded-lg bg-white shadow-sm dark:bg-zinc-700"
transition={
reduceMotion
? { duration: 0 }
: { type: "spring", stiffness: 420, damping: 34 }
}
/>
) : null}
<span className="relative">{phase.label}</span>
</button>
);
})}
</div>
{PHASES.map((phase, index) => (
<div
key={phase.id}
id={`${baseId}-panel-${phase.id}`}
role="tabpanel"
aria-labelledby={`${baseId}-tab-${phase.id}`}
tabIndex={0}
hidden={index !== activeIndex}
className="relative mt-6 rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500"
>
<div className="flex flex-wrap items-center justify-between gap-3">
<StatusChip status={phase.status} />
<span className="text-xs font-medium tabular-nums text-zinc-500 dark:text-zinc-500">
{phase.percent}% done
</span>
</div>
<div
role="progressbar"
aria-valuenow={phase.percent}
aria-valuemin={0}
aria-valuemax={100}
aria-label={`${phase.label} progress`}
className="mt-3 h-1.5 w-full overflow-hidden rounded-full bg-zinc-200 dark:bg-zinc-800"
>
<motion.div
key={`${phase.id}-bar-${activeIndex}`}
className={`h-full rounded-full ${
phase.status === "complete"
? "bg-emerald-500 dark:bg-emerald-400"
: phase.status === "active"
? "bg-amber-500 dark:bg-amber-400"
: "bg-zinc-400 dark:bg-zinc-600"
}`}
initial={reduceMotion ? false : { width: 0 }}
animate={{ width: `${phase.percent}%` }}
transition={
reduceMotion
? { duration: 0 }
: { duration: 0.7, ease: [0.22, 1, 0.36, 1] }
}
style={
reduceMotion ? { width: `${phase.percent}%` } : undefined
}
/>
</div>
<p className="mt-5 text-sm font-semibold text-zinc-900 dark:text-white">
{phase.headline}
</p>
<p className="mt-2 text-sm leading-relaxed text-zinc-600 dark:text-zinc-400">
{phase.note}
</p>
<ul className="mt-5 space-y-2.5">
{phase.items.map((item) => (
<li
key={item}
className="flex items-start gap-2.5 text-sm text-zinc-700 dark:text-zinc-300"
>
<span
aria-hidden="true"
className={`mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full ${
phase.status === "complete"
? "bg-emerald-500 dark:bg-emerald-400"
: phase.status === "active"
? "bg-amber-500 dark:bg-amber-400"
: "bg-zinc-400 dark:bg-zinc-600"
}`}
/>
{item}
</li>
))}
</ul>
</div>
))}
<p className="relative mt-7 border-t border-zinc-200 pt-4 text-xs text-zinc-500 dark:border-zinc-800 dark:text-zinc-500">
Phase {activeIndex + 1} of {PHASES.length} ·{" "}
<span className="text-zinc-700 dark:text-zinc-300">
{activePhase.label}
</span>{" "}
· use the arrow keys to walk the site
</p>
</div>
</div>
</div>
<div
aria-hidden="true"
className="euc-stripes mt-14 h-2.5 w-full rounded-full text-amber-400 lg:mt-20 dark:text-amber-300/80"
/>
</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 Offline
Originaloffline / no-connection state

Error Maintenance
Originalscheduled maintenance page

Error Coming Soon
Originalcoming soon page with countdown and email

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.

