We Turned Our 404 Page Into a Three-Act Magic Trick
Most 404 pages read like an apology note from someone who doesn't mean it. "Oops! We can't find that page." Close tab. We had the same thing.
We changed it. The new 404 is a three-act magic trick: a typewriter monologue unfolds over about seven seconds, a giant 404 crashes in behind the text, and a "Back to the show" button appears. Keyboard-skippable, click-to-skip, and fully accessible. Here's what we built and why.
The Bit
Three lines, timed sequentially:
- Ladies and gentlemen…
- …for my next trick…
- I will make this page disappear.
At 5.1 seconds, a Framer Motion scale-and-fade brings in the 404 in 28vw Playfair Display italic behind the monologue. At 6.5 seconds, the // it worked. monospace tag fades in and the CTA button appears.
The script is pure comedy timing. We tuned it like you would tune an edit: the pause between "ladies and gentlemen" and "for my next trick" needs to breathe. Too fast and it reads as a list. Too slow and people click away before the punchline.
The Component
MagicianMonologue.tsx is a single client component. It manages a phase integer (0 through 5) and five setTimeout calls that advance it. Each act is powered by a useTypewriter hook: an interval that increments a character count, slicing the target string as it goes.
function useTypewriter(text: string, active: boolean, complete: boolean, speed = 55) {
const [chars, setChars] = useState(0);
useEffect(() => {
if (complete) { setChars(text.length); return; }
if (!active) { setChars(0); return; }
let i = 0;
const id = setInterval(() => {
i += 1;
setChars(i);
if (i >= text.length) clearInterval(id);
}, speed);
return () => clearInterval(id);
}, [text, active, complete, speed]);
return text.slice(0, chars);
}
55ms per character. At that rate, a 32-character line finishes in about 1.7 seconds: quick enough to feel crisp, slow enough to read as typed rather than just appearing.
Accessibility First, Not Last
Anyone who has prefers-reduced-motion set at the OS level never sees the animation. Framer Motion's useReducedMotion() hook checks on mount. If it returns true, we call setPhase(5) immediately, the final state renders, and no intervals run.
Skip controls exist for everyone else: click anywhere on the page, or press Enter, Escape, or Space. The skip handler cancels every pending timer with clearTimeout and jumps straight to phase 5. A small "skip ↵" hint in the bottom-right corner makes this discoverable without being obnoxious.
Screen readers get a static <p className="sr-only"> that reads: "404. Page not found. The page you requested does not exist. Return to the homepage." The entire animation is aria-hidden. No screen reader user has to sit through seven seconds of theater.
Testing
Two Playwright tests cover the route. The first emulates reducedMotion: "reduce", confirms the page returns a real 404 HTTP status, and asserts that the 404 text and CTA are immediately visible. The second runs without reduced motion and waits up to 10 seconds for the CTA to appear naturally.
The reduced-motion test is the one that matters. If the accessibility shortcut ever breaks, it fails before the code ships.
The Detail That Gets People
After the monologue lands, the giant 404 behind the text breathes: opacity: [0.7, 0.9, 0.7] on a four-second infinite loop. Most visitors won't notice. The ones who do will look a second time.
That's what a 404 should do: slow someone down just enough that they remember where they landed, then give them a clear way out. Ours does that in seven seconds. Skippable if you don't want it.