HTTP status pages
404, 500, maintenance, offline: when a whole page can't render, say what happened, offer one next step and recover on its own when it can, in the same layout as the boot loading screen.
When a page of its own
A status page replaces the whole main content, so use it only when the whole route can't render: nothing at this URL, no permission, a server error, offline when the page opens. When only part of a page fails, report it in that part and keep the rest usable.
| Case | Use |
|---|---|
| The whole route can't render | Status page |
| One region fails to load (table, chart) | ErrorState in that region, with retry |
| A form fails to submit | Errors on each field or a MessageBar; keep what was typed |
| A brief error that clears itself | Toast or MessageBar |
Lab
Eleven states in the same layout as the boot loading screen. Switch numerals (Kanji / Latin), replay, or go full screen and step through states with previous / next.
Page not found
Nothing lives at this URL.
Status catalog
| Code | Title | When | Main action |
|---|---|---|---|
| 400 | Bad request | URL parameters or submitted data are malformed and can't be fixed in place. | Go back |
| 401 | Sign in to continue | Not signed in, or the session expired. | Sign in |
| 403 | No access | Signed in, but without permission. | Request access |
| 404 | Page not found | Nothing lives at this URL. | Search |
| 410 | This page was removed | Removed on purpose; link to the replacement when there is one. | Go to the new page |
| 429 | That was a bit fast | Rate limit reached; take the wait from the Retry-After header. | Retry |
| 500 | Something went wrong on our side | An unexpected server error, including a 502 from a gateway. | Retry |
| 503 | Down for maintenance | Planned maintenance or temporary overload; count down when there is a Retry-After. | Reload |
| 504 | The server took too long | Timed out at the server (504) or in the browser (408). | Retry |
| Offline | You're offline | The browser lost its connection or a request failed on the network; listen for the online event to retry. | Retry |
| New version | A new version is available | A JS file fails to load after a deploy (a tab opened earlier, file names changed). | Reload |
Writing the copy
The title says what happened in plain words (“Page not found”); the description says why, when known, and what people can do next. The code sits in the small line above the title for anyone reporting the problem, and is never the only text on the page.
Don't blame the user, don't joke when they just lost what they were doing, don't print a stack trace. An error ID (7f3a-91c2) is plain text people can select and copy.
Each page has exactly one main action; the secondary action is a safe way out, such as Home or Switch account.
Mark & vertical code
The mark in the corner changes with the state: one petal missing when the page isn't found, all fallen when it was removed, still a bud when you need to sign in or ask for access, the ink broken on a server error, faded on a timeout, only a trace when offline. On 429 and 503 the brush stroke fills with the wait; when time is up the blossom opens as Retry turns on.
Kanji style: the code runs down the edge like the loading screen, read digit by digit (四〇四); states without a code use words: 圏外 (out of range) when offline, 新版 for a new version. Latin style, for products that don't use kanji: the code sits horizontally in a light weight in the top corner; states without a code leave that corner empty.
Recovering on its own
Temporary states should clear themselves: count down from Retry-After and then enable the button, listen for the online event to retry, and offer a reload when a JS file fails to load after a deploy. After sign-in (401), take people back to the page they were opening.
// 429 / 503: take the wait from the header, don't guess
const wait = Number(response.headers.get("Retry-After")) || 30;
// Offline: retry when the browser reports a connection
window.addEventListener("online", retry, { once: true });
// New version: the route's JS file is gone after a deploy
const Settings = lazy(() => import("./pages/Settings").catch(() => ({ default: NewVersionPage })));A 404 page should return a real 404 status and noindex, so search engines don't index a page that looks normal. Static hosts (GitHub Pages) can only serve 404.html; there, 500 and 503 come from the app itself.
Accessibility
The title is an h1 naming the state; on client-side navigation, move focus to the h1 and update document.title. The vertical code is decorative (aria-hidden); the readable code is in the line above the title.
Announce countdowns through a polite live region at intervals (10 seconds), not every second. Never reload on your own without saying so first. With reduced motion, the brush stroke is already drawn and the petals already lie on the ground.
Do and don't
- Say what happened in plain words, with the code in a secondary line.
- One main action and one safe way out.
- Count down from Retry-After; retry when back online.
- Return a real HTTP status and noindex on 404 pages.
- Send unknown URLs to the home page as if nothing happened.
- Show only “404” or “Error” without an explanation.
- Print stack traces or technical messages to users.
- Reload in a loop while the server is still failing.