08 / 20
All 20 in 20 Tailwind CSS Empty States
- 01Tailwind CSS No Search Results Empty State
- 02Tailwind CSS Onboarding Empty State with Setup Checklist
- 03Tailwind CSS Empty Shopping Cart Page
- 04Tailwind CSS Inbox Zero / All Caught Up Empty State
- 05Tailwind CSS Empty Table State (No Rows Found)
- 06Tailwind CSS Filter No Matches Empty State (Pure CSS)
- 07Tailwind CSS Empty Dashboard / No Data State
- 08Tailwind CSS 500 Internal Server Error Page
- 09Tailwind CSS Empty Wishlist / Favorites Page
- 10Tailwind CSS Empty Notification Dropdown Panel
- 11Tailwind CSS Error State with Retry Button
- 12Tailwind CSS Offline / No Internet Connection Page
- 13Tailwind CSS 403 Forbidden / Access Denied Page
- 14Tailwind CSS 404 Page Not Found Template
- 15Tailwind CSS Empty Trash / Archived Items State
- 16Tailwind CSS Skeleton Loader to Empty State Transition
- 17Accessible Empty State in Tailwind CSS (ARIA Live Region)
- 18Pure Tailwind CSS Illustrated Empty State (No Images)
- 19Tailwind CSS Dark Mode Empty State (dark: Variant)
- 20Tailwind CSS Inline Micro Empty State Component
Tailwind CSS 500 Internal Server Error Page
A 500 page that treats the user as a colleague: a plain-English cause, a retry that actually replays, a copyable incident reference and a live recovery log. The giant 500 is outlined text with [-webkit-text-stroke]; the retry button's three states are data-busy: and data-done: variants.
Nucleo: 44,000+ SVG icons in five families · All Icons Bundle $149
Nucleo · Affiliate link
The code
<div class="tes-08 w-full min-h-svh bg-tes08-bg bg-[radial-gradient(60%_50%_at_20%_0%,color-mix(in_oklab,var(--color-tes08-acc)_14%,transparent),transparent)] font-tes08-sans text-tes08-ink antialiased [color-scheme:dark]">
<header class="flex flex-wrap items-center gap-x-4 gap-y-2 border-b border-tes08-line px-[clamp(1rem,4vw,3rem)] py-4">
<span class="font-semibold tracking-tight">Tideline</span>
<span class="rounded-full border border-tes08-line2 px-2.5 py-1 font-tes08-mono text-[11px] text-tes08-mut">production · eu-west-1</span>
<a href="#" class="ml-auto inline-flex min-h-11 items-center gap-2 text-sm text-tes08-mut hover:text-tes08-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tes08-acc"><i aria-hidden="true" class="size-2 rounded-full bg-tes08-warn"></i>Status page</a>
</header>
<main class="mx-auto grid w-full max-w-6xl items-center gap-10 px-[clamp(1rem,4vw,3rem)] py-[clamp(2rem,7vw,5rem)] lg:grid-cols-[minmax(0,1.1fr)_minmax(0,1fr)]">
<section aria-labelledby="tes08-h" class="grid justify-items-start gap-5">
<p aria-hidden="true" class="font-tes08-sans text-[clamp(5rem,16vw,9.5rem)] leading-[0.85] font-semibold tracking-[-0.06em] text-transparent [-webkit-text-stroke:1.5px_var(--color-tes08-line2)]">500</p>
<h1 id="tes08-h" class="text-[clamp(1.6rem,3.4vw,2.4rem)] leading-tight font-semibold tracking-tight text-balance">The request did not make it back</h1>
<p id="tes08-p" class="max-w-[48ch] text-[15px]/7 text-tes08-mut text-pretty">Our edge accepted it, the upstream service did not answer in time. Nothing you sent has been lost — retry when you are ready.</p>
<div class="flex flex-wrap gap-2.5 pt-1">
<button type="button" id="tes08-retry" class="group/btn inline-flex min-h-11 items-center gap-2 rounded-xl bg-tes08-ink px-5 text-sm font-medium text-tes08-bg transition hover:bg-white active:scale-[.98] data-busy:pointer-events-none data-busy:bg-tes08-panel2 data-busy:text-tes08-ink data-done:bg-tes08-ok data-done:text-tes08-bg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tes08-acc"><span aria-hidden="true" class="hidden size-4 animate-spin rounded-full border-2 border-current border-r-transparent group-data-busy/btn:block motion-reduce:animate-none"></span><span id="tes08-rl">Retry request</span></button>
<button type="button" id="tes08-copy" class="min-h-11 rounded-xl border border-tes08-line2 px-4 text-sm transition hover:bg-tes08-panel2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tes08-acc">Copy incident reference</button>
</div>
<p id="tes08-said" role="status" class="min-h-5 text-xs text-tes08-ok"></p>
<dl class="grid w-full max-w-lg grid-cols-3 gap-px overflow-hidden rounded-xl border border-tes08-line bg-tes08-line text-xs">
<div class="grid gap-1 bg-tes08-panel p-3"><dt class="text-tes08-mut">Reference</dt><dd class="truncate font-tes08-mono">req_9f2c41ba8e</dd></div>
<div class="grid gap-1 bg-tes08-panel p-3"><dt class="text-tes08-mut">Region</dt><dd class="font-tes08-mono">eu-west-1b</dd></div>
<div class="grid gap-1 bg-tes08-panel p-3"><dt class="text-tes08-mut">Time</dt><dd id="tes08-time" class="font-tes08-mono">14:02:11 UTC</dd></div>
</dl>
</section>
<aside aria-label="Recovery log" class="overflow-hidden rounded-2xl border border-tes08-line bg-tes08-panel shadow-[0_40px_80px_-40px_rgb(0_0_0/0.7)]">
<div class="flex items-center gap-2 border-b border-tes08-line px-4 py-3">
<span aria-hidden="true" class="flex gap-1.5"><i class="size-2.5 rounded-full bg-tes08-line2"></i><i class="size-2.5 rounded-full bg-tes08-line2"></i><i class="size-2.5 rounded-full bg-tes08-line2"></i></span>
<span class="ml-2 font-tes08-mono text-xs text-tes08-mut">recovery log</span>
<span id="tes08-state" class="ml-auto rounded-full bg-tes08-warn/15 px-2 py-0.5 font-tes08-mono text-[11px] text-tes08-warn">idle</span>
</div>
<div aria-hidden="true" class="relative h-0.5 overflow-hidden bg-tes08-line"><span id="tes08-prog" hidden class="absolute inset-y-0 w-1/3 animate-tes08-scan bg-tes08-acc motion-reduce:animate-none"></span></div>
<ol id="tes08-lines" class="grid gap-2 p-4 font-tes08-mono text-[12.5px]/5 text-tes08-mut">
<li class="flex gap-3"><span class="text-tes08-faint">14:02:09</span><span>POST /v2/ledger/settle → <b class="font-medium text-tes08-err">upstream timeout</b></span></li>
<li class="flex gap-3"><span class="text-tes08-faint">14:02:10</span><span>circuit breaker opened for ledger-api</span></li>
<li class="flex gap-3"><span class="text-tes08-faint">14:02:11</span><span>request queued for replay</span></li>
</ol>
</aside>
</main>
</div><div class="tes-08 w-full min-h-svh bg-tes08-bg bg-[radial-gradient(60%_50%_at_20%_0%,color-mix(in_oklab,var(--color-tes08-acc)_14%,transparent),transparent)] font-tes08-sans text-tes08-ink antialiased [color-scheme:dark]">
<header class="flex flex-wrap items-center gap-x-4 gap-y-2 border-b border-tes08-line px-[clamp(1rem,4vw,3rem)] py-4">
<span class="font-semibold tracking-tight">Tideline</span>
<span class="rounded-full border border-tes08-line2 px-2.5 py-1 font-tes08-mono text-[11px] text-tes08-mut">production · eu-west-1</span>
<a href="#" class="ml-auto inline-flex min-h-11 items-center gap-2 text-sm text-tes08-mut hover:text-tes08-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tes08-acc"><i aria-hidden="true" class="size-2 rounded-full bg-tes08-warn"></i>Status page</a>
</header>
<main class="mx-auto grid w-full max-w-6xl items-center gap-10 px-[clamp(1rem,4vw,3rem)] py-[clamp(2rem,7vw,5rem)] lg:grid-cols-[minmax(0,1.1fr)_minmax(0,1fr)]">
<section aria-labelledby="tes08-h" class="grid justify-items-start gap-5">
<p aria-hidden="true" class="font-tes08-sans text-[clamp(5rem,16vw,9.5rem)] leading-[0.85] font-semibold tracking-[-0.06em] text-transparent [-webkit-text-stroke:1.5px_var(--color-tes08-line2)]">500</p>
<h1 id="tes08-h" class="text-[clamp(1.6rem,3.4vw,2.4rem)] leading-tight font-semibold tracking-tight text-balance">The request did not make it back</h1>
<p id="tes08-p" class="max-w-[48ch] text-[15px]/7 text-tes08-mut text-pretty">Our edge accepted it, the upstream service did not answer in time. Nothing you sent has been lost — retry when you are ready.</p>
<div class="flex flex-wrap gap-2.5 pt-1">
<button type="button" id="tes08-retry" class="group/btn inline-flex min-h-11 items-center gap-2 rounded-xl bg-tes08-ink px-5 text-sm font-medium text-tes08-bg transition hover:bg-white active:scale-[.98] data-busy:pointer-events-none data-busy:bg-tes08-panel2 data-busy:text-tes08-ink data-done:bg-tes08-ok data-done:text-tes08-bg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tes08-acc"><span aria-hidden="true" class="hidden size-4 animate-spin rounded-full border-2 border-current border-r-transparent group-data-busy/btn:block motion-reduce:animate-none"></span><span id="tes08-rl">Retry request</span></button>
<button type="button" id="tes08-copy" class="min-h-11 rounded-xl border border-tes08-line2 px-4 text-sm transition hover:bg-tes08-panel2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tes08-acc">Copy incident reference</button>
</div>
<p id="tes08-said" role="status" class="min-h-5 text-xs text-tes08-ok"></p>
<dl class="grid w-full max-w-lg grid-cols-3 gap-px overflow-hidden rounded-xl border border-tes08-line bg-tes08-line text-xs">
<div class="grid gap-1 bg-tes08-panel p-3"><dt class="text-tes08-mut">Reference</dt><dd class="truncate font-tes08-mono">req_9f2c41ba8e</dd></div>
<div class="grid gap-1 bg-tes08-panel p-3"><dt class="text-tes08-mut">Region</dt><dd class="font-tes08-mono">eu-west-1b</dd></div>
<div class="grid gap-1 bg-tes08-panel p-3"><dt class="text-tes08-mut">Time</dt><dd id="tes08-time" class="font-tes08-mono">14:02:11 UTC</dd></div>
</dl>
</section>
<aside aria-label="Recovery log" class="overflow-hidden rounded-2xl border border-tes08-line bg-tes08-panel shadow-[0_40px_80px_-40px_rgb(0_0_0/0.7)]">
<div class="flex items-center gap-2 border-b border-tes08-line px-4 py-3">
<span aria-hidden="true" class="flex gap-1.5"><i class="size-2.5 rounded-full bg-tes08-line2"></i><i class="size-2.5 rounded-full bg-tes08-line2"></i><i class="size-2.5 rounded-full bg-tes08-line2"></i></span>
<span class="ml-2 font-tes08-mono text-xs text-tes08-mut">recovery log</span>
<span id="tes08-state" class="ml-auto rounded-full bg-tes08-warn/15 px-2 py-0.5 font-tes08-mono text-[11px] text-tes08-warn">idle</span>
</div>
<div aria-hidden="true" class="relative h-0.5 overflow-hidden bg-tes08-line"><span id="tes08-prog" hidden class="absolute inset-y-0 w-1/3 animate-tes08-scan bg-tes08-acc motion-reduce:animate-none"></span></div>
<ol id="tes08-lines" class="grid gap-2 p-4 font-tes08-mono text-[12.5px]/5 text-tes08-mut">
<li class="flex gap-3"><span class="text-tes08-faint">14:02:09</span><span>POST /v2/ledger/settle → <b class="font-medium text-tes08-err">upstream timeout</b></span></li>
<li class="flex gap-3"><span class="text-tes08-faint">14:02:10</span><span>circuit breaker opened for ledger-api</span></li>
<li class="flex gap-3"><span class="text-tes08-faint">14:02:11</span><span>request queued for replay</span></li>
</ol>
</aside>
</main>
</div>@import "tailwindcss";
@theme {
--color-tes08-bg: #0c0e12;
--color-tes08-bg: oklch(0.15 0.01 255);
--color-tes08-panel: #12151b;
--color-tes08-panel: oklch(0.19 0.012 255);
--color-tes08-panel2: #1a1e26;
--color-tes08-panel2: oklch(0.23 0.014 255);
--color-tes08-line: #232831;
--color-tes08-line: oklch(0.275 0.014 255);
--color-tes08-line2: #3a414e;
--color-tes08-line2: oklch(0.37 0.018 255);
--color-tes08-ink: #eef1f6;
--color-tes08-ink: oklch(0.96 0.006 255);
--color-tes08-mut: #a3abba;
--color-tes08-mut: oklch(0.74 0.02 255);
--color-tes08-faint: #7b8494;
--color-tes08-faint: oklch(0.62 0.02 255);
--color-tes08-acc: #64b5ff;
--color-tes08-acc: oklch(0.75 0.13 245);
--color-tes08-ok: #5ee0a0;
--color-tes08-ok: oklch(0.83 0.15 160);
--color-tes08-warn: #f2b45a;
--color-tes08-warn: oklch(0.81 0.12 72);
--color-tes08-err: #ff8a7a;
--color-tes08-err: oklch(0.76 0.14 28);
--font-tes08-sans: 'Geist', system-ui, sans-serif;
--font-tes08-mono: 'Geist Mono', ui-monospace, monospace;
--animate-tes08-scan: tes08-scan 1.1s cubic-bezier(0.4,0,0.2,1) infinite;
@keyframes tes08-scan {
from {
translate: -100% 0;
}
to {
translate: 300% 0;
}
}
}
/* Root rule: the full-bleed contract, restated in CSS so the stage is sized before the browser
build compiles, plus a guard so the hidden attribute always beats display utilities. */
.tes-08 {
width: 100%;
min-height: 100svh;
}
.tes-08 [hidden] {
display: none !important;
}@import "tailwindcss";
@theme {
--color-tes08-bg: #0c0e12;
--color-tes08-bg: oklch(0.15 0.01 255);
--color-tes08-panel: #12151b;
--color-tes08-panel: oklch(0.19 0.012 255);
--color-tes08-panel2: #1a1e26;
--color-tes08-panel2: oklch(0.23 0.014 255);
--color-tes08-line: #232831;
--color-tes08-line: oklch(0.275 0.014 255);
--color-tes08-line2: #3a414e;
--color-tes08-line2: oklch(0.37 0.018 255);
--color-tes08-ink: #eef1f6;
--color-tes08-ink: oklch(0.96 0.006 255);
--color-tes08-mut: #a3abba;
--color-tes08-mut: oklch(0.74 0.02 255);
--color-tes08-faint: #7b8494;
--color-tes08-faint: oklch(0.62 0.02 255);
--color-tes08-acc: #64b5ff;
--color-tes08-acc: oklch(0.75 0.13 245);
--color-tes08-ok: #5ee0a0;
--color-tes08-ok: oklch(0.83 0.15 160);
--color-tes08-warn: #f2b45a;
--color-tes08-warn: oklch(0.81 0.12 72);
--color-tes08-err: #ff8a7a;
--color-tes08-err: oklch(0.76 0.14 28);
--font-tes08-sans: 'Geist', system-ui, sans-serif;
--font-tes08-mono: 'Geist Mono', ui-monospace, monospace;
--animate-tes08-scan: tes08-scan 1.1s cubic-bezier(0.4,0,0.2,1) infinite;
@keyframes tes08-scan {
from {
translate: -100% 0;
}
to {
translate: 300% 0;
}
}
}
/* Root rule: the full-bleed contract, restated in CSS so the stage is sized before the browser
build compiles, plus a guard so the hidden attribute always beats display utilities. */
.tes-08 {
width: 100%;
min-height: 100svh;
}
.tes-08 [hidden] {
display: none !important;
}(() => {
const root = document.querySelector('.tes-08');
if (!root || root.dataset.wired) return;
root.dataset.wired = '1';
const $ = (s) => root.querySelector(s);
const retry = $('#tes08-retry'), lines = $('#tes08-lines'), state = $('#tes08-state');
const stamp = () => new Date().toISOString().slice(11, 19);
const line = (html) => {
const li = document.createElement('li');
li.className = 'flex gap-3 transition-[opacity,translate] duration-300 starting:-translate-x-1 starting:opacity-0';
li.innerHTML = '<span class="text-tes08-faint">' + stamp() + '</span><span>' + html + '</span>';
lines.appendChild(li);
};
$('#tes08-time').textContent = stamp() + ' UTC';
retry.addEventListener('click', () => {
if (retry.hasAttribute('data-busy') || retry.hasAttribute('data-done')) return;
retry.setAttribute('data-busy', ''); $('#tes08-rl').textContent = 'Retrying';
$('#tes08-prog').hidden = false; state.textContent = 'replaying';
line('replaying req_9f2c41ba8e');
setTimeout(() => line('ledger-api handshake ok'), 700);
setTimeout(() => {
line('<b class="font-medium text-tes08-ok">settled \u2014 200 OK</b>');
$('#tes08-prog').hidden = true;
state.textContent = 'recovered'; state.className = 'ml-auto rounded-full bg-tes08-ok/15 px-2 py-0.5 font-tes08-mono text-[11px] text-tes08-ok';
retry.removeAttribute('data-busy'); retry.setAttribute('data-done', ''); $('#tes08-rl').textContent = 'Request completed';
$('#tes08-h').textContent = 'It went through this time';
$('#tes08-p').textContent = 'The replay settled against the ledger. You can carry on where you left off.';
}, 1600);
});
$('#tes08-copy').addEventListener('click', () => {
const ref = 'req_9f2c41ba8e';
const said = $('#tes08-said');
const fallback = () => {
const t = document.createElement('textarea'); t.value = ref; document.body.appendChild(t); t.select();
let ok = false; try { ok = document.execCommand('copy'); } catch (e) {}
t.remove(); said.textContent = ok ? 'Reference copied.' : 'Copy blocked \u2014 the reference is ' + ref;
};
if (navigator.clipboard) navigator.clipboard.writeText(ref).then(() => { said.textContent = 'Reference copied.'; }, fallback);
else fallback();
});
})();(() => {
const root = document.querySelector('.tes-08');
if (!root || root.dataset.wired) return;
root.dataset.wired = '1';
const $ = (s) => root.querySelector(s);
const retry = $('#tes08-retry'), lines = $('#tes08-lines'), state = $('#tes08-state');
const stamp = () => new Date().toISOString().slice(11, 19);
const line = (html) => {
const li = document.createElement('li');
li.className = 'flex gap-3 transition-[opacity,translate] duration-300 starting:-translate-x-1 starting:opacity-0';
li.innerHTML = '<span class="text-tes08-faint">' + stamp() + '</span><span>' + html + '</span>';
lines.appendChild(li);
};
$('#tes08-time').textContent = stamp() + ' UTC';
retry.addEventListener('click', () => {
if (retry.hasAttribute('data-busy') || retry.hasAttribute('data-done')) return;
retry.setAttribute('data-busy', ''); $('#tes08-rl').textContent = 'Retrying';
$('#tes08-prog').hidden = false; state.textContent = 'replaying';
line('replaying req_9f2c41ba8e');
setTimeout(() => line('ledger-api handshake ok'), 700);
setTimeout(() => {
line('<b class="font-medium text-tes08-ok">settled \u2014 200 OK</b>');
$('#tes08-prog').hidden = true;
state.textContent = 'recovered'; state.className = 'ml-auto rounded-full bg-tes08-ok/15 px-2 py-0.5 font-tes08-mono text-[11px] text-tes08-ok';
retry.removeAttribute('data-busy'); retry.setAttribute('data-done', ''); $('#tes08-rl').textContent = 'Request completed';
$('#tes08-h').textContent = 'It went through this time';
$('#tes08-p').textContent = 'The replay settled against the ledger. You can carry on where you left off.';
}, 1600);
});
$('#tes08-copy').addEventListener('click', () => {
const ref = 'req_9f2c41ba8e';
const said = $('#tes08-said');
const fallback = () => {
const t = document.createElement('textarea'); t.value = ref; document.body.appendChild(t); t.select();
let ok = false; try { ok = document.execCommand('copy'); } catch (e) {}
t.remove(); said.textContent = ok ? 'Reference copied.' : 'Copy blocked \u2014 the reference is ' + ref;
};
if (navigator.clipboard) navigator.clipboard.writeText(ref).then(() => { said.textContent = 'Reference copied.'; }, fallback);
else fallback();
});
})();Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tes08-bg: #0c0e12;
--color-tes08-bg: oklch(0.15 0.01 255);
--color-tes08-panel: #12151b;
--color-tes08-panel: oklch(0.19 0.012 255);
--color-tes08-panel2: #1a1e26;
--color-tes08-panel2: oklch(0.23 0.014 255);
--color-tes08-line: #232831;
--color-tes08-line: oklch(0.275 0.014 255);
--color-tes08-line2: #3a414e;
--color-tes08-line2: oklch(0.37 0.018 255);
--color-tes08-ink: #eef1f6;
--color-tes08-ink: oklch(0.96 0.006 255);
--color-tes08-mut: #a3abba;
--color-tes08-mut: oklch(0.74 0.02 255);
--color-tes08-faint: #7b8494;
--color-tes08-faint: oklch(0.62 0.02 255);
--color-tes08-acc: #64b5ff;
--color-tes08-acc: oklch(0.75 0.13 245);
--color-tes08-ok: #5ee0a0;
--color-tes08-ok: oklch(0.83 0.15 160);
--color-tes08-warn: #f2b45a;
--color-tes08-warn: oklch(0.81 0.12 72);
--color-tes08-err: #ff8a7a;
--color-tes08-err: oklch(0.76 0.14 28);
--font-tes08-sans: 'Geist', system-ui, sans-serif;
--font-tes08-mono: 'Geist Mono', ui-monospace, monospace;
--animate-tes08-scan: tes08-scan 1.1s cubic-bezier(0.4,0,0.2,1) infinite;
@keyframes tes08-scan {
from {
translate: -100% 0;
}
to {
translate: 300% 0;
}
}
}
/* Root rule: the full-bleed contract, restated in CSS so the stage is sized before the browser
build compiles, plus a guard so the hidden attribute always beats display utilities. */
.tes-08 {
width: 100%;
min-height: 100svh;
}
.tes-08 [hidden] {
display: none !important;
}@import "tailwindcss";
@theme {
--color-tes08-bg: #0c0e12;
--color-tes08-bg: oklch(0.15 0.01 255);
--color-tes08-panel: #12151b;
--color-tes08-panel: oklch(0.19 0.012 255);
--color-tes08-panel2: #1a1e26;
--color-tes08-panel2: oklch(0.23 0.014 255);
--color-tes08-line: #232831;
--color-tes08-line: oklch(0.275 0.014 255);
--color-tes08-line2: #3a414e;
--color-tes08-line2: oklch(0.37 0.018 255);
--color-tes08-ink: #eef1f6;
--color-tes08-ink: oklch(0.96 0.006 255);
--color-tes08-mut: #a3abba;
--color-tes08-mut: oklch(0.74 0.02 255);
--color-tes08-faint: #7b8494;
--color-tes08-faint: oklch(0.62 0.02 255);
--color-tes08-acc: #64b5ff;
--color-tes08-acc: oklch(0.75 0.13 245);
--color-tes08-ok: #5ee0a0;
--color-tes08-ok: oklch(0.83 0.15 160);
--color-tes08-warn: #f2b45a;
--color-tes08-warn: oklch(0.81 0.12 72);
--color-tes08-err: #ff8a7a;
--color-tes08-err: oklch(0.76 0.14 28);
--font-tes08-sans: 'Geist', system-ui, sans-serif;
--font-tes08-mono: 'Geist Mono', ui-monospace, monospace;
--animate-tes08-scan: tes08-scan 1.1s cubic-bezier(0.4,0,0.2,1) infinite;
@keyframes tes08-scan {
from {
translate: -100% 0;
}
to {
translate: 300% 0;
}
}
}
/* Root rule: the full-bleed contract, restated in CSS so the stage is sized before the browser
build compiles, plus a guard so the hidden attribute always beats display utilities. */
.tes-08 {
width: 100%;
min-height: 100svh;
}
.tes-08 [hidden] {
display: none !important;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400..700&family=Geist+Mono:wght@400..600&family=Instrument+Serif:ital@0;1&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400..700&family=Geist+Mono:wght@400..600&family=Instrument+Serif:ital@0;1&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
.est-08__retry[data-busy]and[data-done]becamedata-busy:/data-done:variants; the spinner is agroup-data-busy/btn:blockchild.- UI upgrade from the CSS original: "Copy incident reference" was a dead link — it now copies (with a sandbox-safe fallback) and announces the result.
- UI upgrade: outlined 500 numerals, an indeterminate scan bar during replay, colour-coded log lines that slide in with
starting:, and real UTC timestamps.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind CSS 500 Internal Server Error Page
Source: https://codefronts.com/snippets/tailwind-css-empty-states/tailwind-css-500-internal-server-error-page/
A 500 page that treats the user as a colleague: a plain-English cause, a retry that actually replays, a copyable incident reference and a live recovery log. The giant 500 is outlined text with [-webkit-text-stroke]; the retry button's three states are data-busy: and data-done: variants.
## HTML
```html
<div class="tes-08 w-full min-h-svh bg-tes08-bg bg-[radial-gradient(60%_50%_at_20%_0%,color-mix(in_oklab,var(--color-tes08-acc)_14%,transparent),transparent)] font-tes08-sans text-tes08-ink antialiased [color-scheme:dark]">
<header class="flex flex-wrap items-center gap-x-4 gap-y-2 border-b border-tes08-line px-[clamp(1rem,4vw,3rem)] py-4">
<span class="font-semibold tracking-tight">Tideline</span>
<span class="rounded-full border border-tes08-line2 px-2.5 py-1 font-tes08-mono text-[11px] text-tes08-mut">production · eu-west-1</span>
<a href="#" class="ml-auto inline-flex min-h-11 items-center gap-2 text-sm text-tes08-mut hover:text-tes08-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tes08-acc"><i aria-hidden="true" class="size-2 rounded-full bg-tes08-warn"></i>Status page</a>
</header>
<main class="mx-auto grid w-full max-w-6xl items-center gap-10 px-[clamp(1rem,4vw,3rem)] py-[clamp(2rem,7vw,5rem)] lg:grid-cols-[minmax(0,1.1fr)_minmax(0,1fr)]">
<section aria-labelledby="tes08-h" class="grid justify-items-start gap-5">
<p aria-hidden="true" class="font-tes08-sans text-[clamp(5rem,16vw,9.5rem)] leading-[0.85] font-semibold tracking-[-0.06em] text-transparent [-webkit-text-stroke:1.5px_var(--color-tes08-line2)]">500</p>
<h1 id="tes08-h" class="text-[clamp(1.6rem,3.4vw,2.4rem)] leading-tight font-semibold tracking-tight text-balance">The request did not make it back</h1>
<p id="tes08-p" class="max-w-[48ch] text-[15px]/7 text-tes08-mut text-pretty">Our edge accepted it, the upstream service did not answer in time. Nothing you sent has been lost — retry when you are ready.</p>
<div class="flex flex-wrap gap-2.5 pt-1">
<button type="button" id="tes08-retry" class="group/btn inline-flex min-h-11 items-center gap-2 rounded-xl bg-tes08-ink px-5 text-sm font-medium text-tes08-bg transition hover:bg-white active:scale-[.98] data-busy:pointer-events-none data-busy:bg-tes08-panel2 data-busy:text-tes08-ink data-done:bg-tes08-ok data-done:text-tes08-bg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tes08-acc"><span aria-hidden="true" class="hidden size-4 animate-spin rounded-full border-2 border-current border-r-transparent group-data-busy/btn:block motion-reduce:animate-none"></span><span id="tes08-rl">Retry request</span></button>
<button type="button" id="tes08-copy" class="min-h-11 rounded-xl border border-tes08-line2 px-4 text-sm transition hover:bg-tes08-panel2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tes08-acc">Copy incident reference</button>
</div>
<p id="tes08-said" role="status" class="min-h-5 text-xs text-tes08-ok"></p>
<dl class="grid w-full max-w-lg grid-cols-3 gap-px overflow-hidden rounded-xl border border-tes08-line bg-tes08-line text-xs">
<div class="grid gap-1 bg-tes08-panel p-3"><dt class="text-tes08-mut">Reference</dt><dd class="truncate font-tes08-mono">req_9f2c41ba8e</dd></div>
<div class="grid gap-1 bg-tes08-panel p-3"><dt class="text-tes08-mut">Region</dt><dd class="font-tes08-mono">eu-west-1b</dd></div>
<div class="grid gap-1 bg-tes08-panel p-3"><dt class="text-tes08-mut">Time</dt><dd id="tes08-time" class="font-tes08-mono">14:02:11 UTC</dd></div>
</dl>
</section>
<aside aria-label="Recovery log" class="overflow-hidden rounded-2xl border border-tes08-line bg-tes08-panel shadow-[0_40px_80px_-40px_rgb(0_0_0/0.7)]">
<div class="flex items-center gap-2 border-b border-tes08-line px-4 py-3">
<span aria-hidden="true" class="flex gap-1.5"><i class="size-2.5 rounded-full bg-tes08-line2"></i><i class="size-2.5 rounded-full bg-tes08-line2"></i><i class="size-2.5 rounded-full bg-tes08-line2"></i></span>
<span class="ml-2 font-tes08-mono text-xs text-tes08-mut">recovery log</span>
<span id="tes08-state" class="ml-auto rounded-full bg-tes08-warn/15 px-2 py-0.5 font-tes08-mono text-[11px] text-tes08-warn">idle</span>
</div>
<div aria-hidden="true" class="relative h-0.5 overflow-hidden bg-tes08-line"><span id="tes08-prog" hidden class="absolute inset-y-0 w-1/3 animate-tes08-scan bg-tes08-acc motion-reduce:animate-none"></span></div>
<ol id="tes08-lines" class="grid gap-2 p-4 font-tes08-mono text-[12.5px]/5 text-tes08-mut">
<li class="flex gap-3"><span class="text-tes08-faint">14:02:09</span><span>POST /v2/ledger/settle → <b class="font-medium text-tes08-err">upstream timeout</b></span></li>
<li class="flex gap-3"><span class="text-tes08-faint">14:02:10</span><span>circuit breaker opened for ledger-api</span></li>
<li class="flex gap-3"><span class="text-tes08-faint">14:02:11</span><span>request queued for replay</span></li>
</ol>
</aside>
</main>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tes08-bg: #0c0e12;
--color-tes08-bg: oklch(0.15 0.01 255);
--color-tes08-panel: #12151b;
--color-tes08-panel: oklch(0.19 0.012 255);
--color-tes08-panel2: #1a1e26;
--color-tes08-panel2: oklch(0.23 0.014 255);
--color-tes08-line: #232831;
--color-tes08-line: oklch(0.275 0.014 255);
--color-tes08-line2: #3a414e;
--color-tes08-line2: oklch(0.37 0.018 255);
--color-tes08-ink: #eef1f6;
--color-tes08-ink: oklch(0.96 0.006 255);
--color-tes08-mut: #a3abba;
--color-tes08-mut: oklch(0.74 0.02 255);
--color-tes08-faint: #7b8494;
--color-tes08-faint: oklch(0.62 0.02 255);
--color-tes08-acc: #64b5ff;
--color-tes08-acc: oklch(0.75 0.13 245);
--color-tes08-ok: #5ee0a0;
--color-tes08-ok: oklch(0.83 0.15 160);
--color-tes08-warn: #f2b45a;
--color-tes08-warn: oklch(0.81 0.12 72);
--color-tes08-err: #ff8a7a;
--color-tes08-err: oklch(0.76 0.14 28);
--font-tes08-sans: 'Geist', system-ui, sans-serif;
--font-tes08-mono: 'Geist Mono', ui-monospace, monospace;
--animate-tes08-scan: tes08-scan 1.1s cubic-bezier(0.4,0,0.2,1) infinite;
@keyframes tes08-scan {
from {
translate: -100% 0;
}
to {
translate: 300% 0;
}
}
}
/* Root rule: the full-bleed contract, restated in CSS so the stage is sized before the browser
build compiles, plus a guard so the hidden attribute always beats display utilities. */
.tes-08 {
width: 100%;
min-height: 100svh;
}
.tes-08 [hidden] {
display: none !important;
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('.tes-08');
if (!root || root.dataset.wired) return;
root.dataset.wired = '1';
const $ = (s) => root.querySelector(s);
const retry = $('#tes08-retry'), lines = $('#tes08-lines'), state = $('#tes08-state');
const stamp = () => new Date().toISOString().slice(11, 19);
const line = (html) => {
const li = document.createElement('li');
li.className = 'flex gap-3 transition-[opacity,translate] duration-300 starting:-translate-x-1 starting:opacity-0';
li.innerHTML = '<span class="text-tes08-faint">' + stamp() + '</span><span>' + html + '</span>';
lines.appendChild(li);
};
$('#tes08-time').textContent = stamp() + ' UTC';
retry.addEventListener('click', () => {
if (retry.hasAttribute('data-busy') || retry.hasAttribute('data-done')) return;
retry.setAttribute('data-busy', ''); $('#tes08-rl').textContent = 'Retrying';
$('#tes08-prog').hidden = false; state.textContent = 'replaying';
line('replaying req_9f2c41ba8e');
setTimeout(() => line('ledger-api handshake ok'), 700);
setTimeout(() => {
line('<b class="font-medium text-tes08-ok">settled \u2014 200 OK</b>');
$('#tes08-prog').hidden = true;
state.textContent = 'recovered'; state.className = 'ml-auto rounded-full bg-tes08-ok/15 px-2 py-0.5 font-tes08-mono text-[11px] text-tes08-ok';
retry.removeAttribute('data-busy'); retry.setAttribute('data-done', ''); $('#tes08-rl').textContent = 'Request completed';
$('#tes08-h').textContent = 'It went through this time';
$('#tes08-p').textContent = 'The replay settled against the ledger. You can carry on where you left off.';
}, 1600);
});
$('#tes08-copy').addEventListener('click', () => {
const ref = 'req_9f2c41ba8e';
const said = $('#tes08-said');
const fallback = () => {
const t = document.createElement('textarea'); t.value = ref; document.body.appendChild(t); t.select();
let ok = false; try { ok = document.execCommand('copy'); } catch (e) {}
t.remove(); said.textContent = ok ? 'Reference copied.' : 'Copy blocked \u2014 the reference is ' + ref;
};
if (navigator.clipboard) navigator.clipboard.writeText(ref).then(() => { said.textContent = 'Reference copied.'; }, fallback);
else fallback();
});
})();
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind CSS 500 Internal Server Error Page
Source: https://codefronts.com/snippets/tailwind-css-empty-states/tailwind-css-500-internal-server-error-page/
A 500 page that treats the user as a colleague: a plain-English cause, a retry that actually replays, a copyable incident reference and a live recovery log. The giant 500 is outlined text with [-webkit-text-stroke]; the retry button's three states are data-busy: and data-done: variants.
## HTML
```html
<div class="tes-08 w-full min-h-svh bg-tes08-bg bg-[radial-gradient(60%_50%_at_20%_0%,color-mix(in_oklab,var(--color-tes08-acc)_14%,transparent),transparent)] font-tes08-sans text-tes08-ink antialiased [color-scheme:dark]">
<header class="flex flex-wrap items-center gap-x-4 gap-y-2 border-b border-tes08-line px-[clamp(1rem,4vw,3rem)] py-4">
<span class="font-semibold tracking-tight">Tideline</span>
<span class="rounded-full border border-tes08-line2 px-2.5 py-1 font-tes08-mono text-[11px] text-tes08-mut">production · eu-west-1</span>
<a href="#" class="ml-auto inline-flex min-h-11 items-center gap-2 text-sm text-tes08-mut hover:text-tes08-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tes08-acc"><i aria-hidden="true" class="size-2 rounded-full bg-tes08-warn"></i>Status page</a>
</header>
<main class="mx-auto grid w-full max-w-6xl items-center gap-10 px-[clamp(1rem,4vw,3rem)] py-[clamp(2rem,7vw,5rem)] lg:grid-cols-[minmax(0,1.1fr)_minmax(0,1fr)]">
<section aria-labelledby="tes08-h" class="grid justify-items-start gap-5">
<p aria-hidden="true" class="font-tes08-sans text-[clamp(5rem,16vw,9.5rem)] leading-[0.85] font-semibold tracking-[-0.06em] text-transparent [-webkit-text-stroke:1.5px_var(--color-tes08-line2)]">500</p>
<h1 id="tes08-h" class="text-[clamp(1.6rem,3.4vw,2.4rem)] leading-tight font-semibold tracking-tight text-balance">The request did not make it back</h1>
<p id="tes08-p" class="max-w-[48ch] text-[15px]/7 text-tes08-mut text-pretty">Our edge accepted it, the upstream service did not answer in time. Nothing you sent has been lost — retry when you are ready.</p>
<div class="flex flex-wrap gap-2.5 pt-1">
<button type="button" id="tes08-retry" class="group/btn inline-flex min-h-11 items-center gap-2 rounded-xl bg-tes08-ink px-5 text-sm font-medium text-tes08-bg transition hover:bg-white active:scale-[.98] data-busy:pointer-events-none data-busy:bg-tes08-panel2 data-busy:text-tes08-ink data-done:bg-tes08-ok data-done:text-tes08-bg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tes08-acc"><span aria-hidden="true" class="hidden size-4 animate-spin rounded-full border-2 border-current border-r-transparent group-data-busy/btn:block motion-reduce:animate-none"></span><span id="tes08-rl">Retry request</span></button>
<button type="button" id="tes08-copy" class="min-h-11 rounded-xl border border-tes08-line2 px-4 text-sm transition hover:bg-tes08-panel2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tes08-acc">Copy incident reference</button>
</div>
<p id="tes08-said" role="status" class="min-h-5 text-xs text-tes08-ok"></p>
<dl class="grid w-full max-w-lg grid-cols-3 gap-px overflow-hidden rounded-xl border border-tes08-line bg-tes08-line text-xs">
<div class="grid gap-1 bg-tes08-panel p-3"><dt class="text-tes08-mut">Reference</dt><dd class="truncate font-tes08-mono">req_9f2c41ba8e</dd></div>
<div class="grid gap-1 bg-tes08-panel p-3"><dt class="text-tes08-mut">Region</dt><dd class="font-tes08-mono">eu-west-1b</dd></div>
<div class="grid gap-1 bg-tes08-panel p-3"><dt class="text-tes08-mut">Time</dt><dd id="tes08-time" class="font-tes08-mono">14:02:11 UTC</dd></div>
</dl>
</section>
<aside aria-label="Recovery log" class="overflow-hidden rounded-2xl border border-tes08-line bg-tes08-panel shadow-[0_40px_80px_-40px_rgb(0_0_0/0.7)]">
<div class="flex items-center gap-2 border-b border-tes08-line px-4 py-3">
<span aria-hidden="true" class="flex gap-1.5"><i class="size-2.5 rounded-full bg-tes08-line2"></i><i class="size-2.5 rounded-full bg-tes08-line2"></i><i class="size-2.5 rounded-full bg-tes08-line2"></i></span>
<span class="ml-2 font-tes08-mono text-xs text-tes08-mut">recovery log</span>
<span id="tes08-state" class="ml-auto rounded-full bg-tes08-warn/15 px-2 py-0.5 font-tes08-mono text-[11px] text-tes08-warn">idle</span>
</div>
<div aria-hidden="true" class="relative h-0.5 overflow-hidden bg-tes08-line"><span id="tes08-prog" hidden class="absolute inset-y-0 w-1/3 animate-tes08-scan bg-tes08-acc motion-reduce:animate-none"></span></div>
<ol id="tes08-lines" class="grid gap-2 p-4 font-tes08-mono text-[12.5px]/5 text-tes08-mut">
<li class="flex gap-3"><span class="text-tes08-faint">14:02:09</span><span>POST /v2/ledger/settle → <b class="font-medium text-tes08-err">upstream timeout</b></span></li>
<li class="flex gap-3"><span class="text-tes08-faint">14:02:10</span><span>circuit breaker opened for ledger-api</span></li>
<li class="flex gap-3"><span class="text-tes08-faint">14:02:11</span><span>request queued for replay</span></li>
</ol>
</aside>
</main>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tes08-bg: #0c0e12;
--color-tes08-bg: oklch(0.15 0.01 255);
--color-tes08-panel: #12151b;
--color-tes08-panel: oklch(0.19 0.012 255);
--color-tes08-panel2: #1a1e26;
--color-tes08-panel2: oklch(0.23 0.014 255);
--color-tes08-line: #232831;
--color-tes08-line: oklch(0.275 0.014 255);
--color-tes08-line2: #3a414e;
--color-tes08-line2: oklch(0.37 0.018 255);
--color-tes08-ink: #eef1f6;
--color-tes08-ink: oklch(0.96 0.006 255);
--color-tes08-mut: #a3abba;
--color-tes08-mut: oklch(0.74 0.02 255);
--color-tes08-faint: #7b8494;
--color-tes08-faint: oklch(0.62 0.02 255);
--color-tes08-acc: #64b5ff;
--color-tes08-acc: oklch(0.75 0.13 245);
--color-tes08-ok: #5ee0a0;
--color-tes08-ok: oklch(0.83 0.15 160);
--color-tes08-warn: #f2b45a;
--color-tes08-warn: oklch(0.81 0.12 72);
--color-tes08-err: #ff8a7a;
--color-tes08-err: oklch(0.76 0.14 28);
--font-tes08-sans: 'Geist', system-ui, sans-serif;
--font-tes08-mono: 'Geist Mono', ui-monospace, monospace;
--animate-tes08-scan: tes08-scan 1.1s cubic-bezier(0.4,0,0.2,1) infinite;
@keyframes tes08-scan {
from {
translate: -100% 0;
}
to {
translate: 300% 0;
}
}
}
/* Root rule: the full-bleed contract, restated in CSS so the stage is sized before the browser
build compiles, plus a guard so the hidden attribute always beats display utilities. */
.tes-08 {
width: 100%;
min-height: 100svh;
}
.tes-08 [hidden] {
display: none !important;
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('.tes-08');
if (!root || root.dataset.wired) return;
root.dataset.wired = '1';
const $ = (s) => root.querySelector(s);
const retry = $('#tes08-retry'), lines = $('#tes08-lines'), state = $('#tes08-state');
const stamp = () => new Date().toISOString().slice(11, 19);
const line = (html) => {
const li = document.createElement('li');
li.className = 'flex gap-3 transition-[opacity,translate] duration-300 starting:-translate-x-1 starting:opacity-0';
li.innerHTML = '<span class="text-tes08-faint">' + stamp() + '</span><span>' + html + '</span>';
lines.appendChild(li);
};
$('#tes08-time').textContent = stamp() + ' UTC';
retry.addEventListener('click', () => {
if (retry.hasAttribute('data-busy') || retry.hasAttribute('data-done')) return;
retry.setAttribute('data-busy', ''); $('#tes08-rl').textContent = 'Retrying';
$('#tes08-prog').hidden = false; state.textContent = 'replaying';
line('replaying req_9f2c41ba8e');
setTimeout(() => line('ledger-api handshake ok'), 700);
setTimeout(() => {
line('<b class="font-medium text-tes08-ok">settled \u2014 200 OK</b>');
$('#tes08-prog').hidden = true;
state.textContent = 'recovered'; state.className = 'ml-auto rounded-full bg-tes08-ok/15 px-2 py-0.5 font-tes08-mono text-[11px] text-tes08-ok';
retry.removeAttribute('data-busy'); retry.setAttribute('data-done', ''); $('#tes08-rl').textContent = 'Request completed';
$('#tes08-h').textContent = 'It went through this time';
$('#tes08-p').textContent = 'The replay settled against the ledger. You can carry on where you left off.';
}, 1600);
});
$('#tes08-copy').addEventListener('click', () => {
const ref = 'req_9f2c41ba8e';
const said = $('#tes08-said');
const fallback = () => {
const t = document.createElement('textarea'); t.value = ref; document.body.appendChild(t); t.select();
let ok = false; try { ok = document.execCommand('copy'); } catch (e) {}
t.remove(); said.textContent = ok ? 'Reference copied.' : 'Copy blocked \u2014 the reference is ' + ref;
};
if (navigator.clipboard) navigator.clipboard.writeText(ref).then(() => { said.textContent = 'Reference copied.'; }, fallback);
else fallback();
});
})();
```How this works
The button narrates the recovery. One element, three looks:
<button class="bg-tes08-ink data-busy:bg-tes08-panel2 data-done:bg-tes08-ok">Copy that works in sandboxes. "Copy incident reference" tries navigator.clipboard, falls back to execCommand, and announces the result in a role="status" node either way.
Make it yours
- Swap the request id, region and log lines for values from your edge.
- Change replay timing in the two
setTimeoutcalls. - Accent:
--color-tes08-acc; success:--color-tes08-ok.
Gotchas — read before shipping
-webkit-text-strokeis prefixed everywhere but universally supported; give the numeralsaria-hidden— the heading explains the error.- Never auto-retry non-idempotent requests; the copy promises "nothing you sent has been lost" only because the edge queued it.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Clipboard falls back to execCommand where the async API is blocked.