Every other page on a site gets designed. The error page gets a centered 404 and a link to the homepage, written in the last hour before launch, and then it quietly serves more traffic than most of the marketing pages that took a week. Broken inbound links, a renamed docs URL that someone bookmarked two years ago, an expired magic link, a rate limit during a launch spike, a card that failed to renew. Each of those is a different problem with a different fix, and each of them lands on the same apologetic dead end.
The new Error category treats that dead end as a real screen. Thirty-three blocks, six of them free, covering not only the 404 but the whole family of states a request can end in: server failures, permission walls, expired links, rate limits, planned downtime, billing lockouts and lost connections. Rather than walk through all of them, this post explains eleven, grouped by the situation they belong to.
A status code is a promise, and the page has to keep it
Before design, semantics. The status code is what search engines, monitoring tools and browsers actually read. The page is what humans read. When those two disagree, something downstream breaks.
| Code | Means | Reach for it when |
|---|---|---|
400 | The request itself was malformed | A query string or payload cannot be parsed at all |
401 | Not authenticated | There is no valid session, so signing in would fix it |
402 | Payment required | A subscription lapsed or a balance is unpaid |
403 | Authenticated, still not allowed | Signing in again changes nothing; a permission has to be granted |
404 | Not found | The address may or may not have existed, and may exist later |
410 | Gone, on purpose | The content was removed deliberately and is not coming back |
429 | Too many requests | A rate limit was hit, and waiting will clear it |
451 | Unavailable for legal reasons | Licensing or a legal order blocks this region |
500 | Something broke on the server | The failure is yours, not the visitor's |
503 | Temporarily unavailable | Planned maintenance or an overloaded service |
Four distinctions are worth the extra minute:
401 is not 403. A signed-out visitor gets 401, and the fix is a sign-in form. A signed-in visitor without permission gets 403, and showing them a sign-in form is a small insult, because they are already signed in. The page for the second case has to say who they are and how access gets granted.
410 is not 404. A 404 says "nothing here, maybe later." A 410 says "this existed, we removed it, stop asking." Search engines treat it as the stronger removal signal, so use it for content you retired on purpose and keep the URL alive with an explanation rather than letting it rot into a generic miss.
503 is not a 200 with a maintenance message. Serving your maintenance page with a success status tells every crawler that your content is now a maintenance notice. A 503 with a Retry-After header tells them to come back later and leaves your rankings alone.
A soft 404 is the most common mistake of all. Rendering "page not found" while returning 200 means the page reads as real content to everything except a human. If the page says not found, the response has to say it too.
How this maps onto Next.js
In the App Router, not-found.tsx renders with a real 404, and calling
notFound() from next/navigation routes there. error.tsx is a client
component that receives error and a reset function, which is exactly what
the retry buttons in these blocks are built to call. Statuses without a file
convention, 410 and 429 among them, are set in a route handler or in
middleware while the same block renders the body.
What installs
Every block is one React component, and the CLI resolves the whole tree in a single command:
npx shadcn add "https://ui.beste.co/r/error15?email=YOUR_EMAIL&license_key=YOUR_KEY"That drops the block at components/beste/block/error15.tsx, plus the small primitives it renders through, and nothing else. No runtime dependency on us, no license check in your bundle, no service to sign up for. The free blocks skip the query string entirely. If you are on Base UI rather than Radix, swap /r/ for /r-base/ and you get the Base UI build of the same component, and the installation docs cover the license setup once.
The 404 is not one page
Most teams ship a single 404 for the whole site. That is a reasonable start and a poor finish, because a missing product, a renamed article and a mistyped marketing URL are three different failures with three different exits.
The baseline still matters. error1 is the version most sites need on day one: the code, the message, two actions and a short row of the pages people actually want.
Once a site is large enough that no shortlist of links can cover it, search beats guessing. error2 puts a real search field directly under the message and hands the query to a callback, so it routes into whatever search you already run.
Documentation sites have a narrower problem: the page did not vanish, it moved. error9 prints the address that failed, struck through in monospace, and offers the closest match underneath, which turns a dead bookmark into one click instead of a search session.
A store has the opposite problem: the visitor knew exactly what they wanted and it is gone. Sending them to the homepage ends the session, so error11 keeps the intent alive by putting four products under the apology.
Publishers get the same shape with different content. error12 fills the space under a dead post URL with the three articles people are actually reading, and error32 does it in a quieter editorial layout for a renamed essay. For a site too large for either, error13 turns the 404 into a full directory, four columns of grouped sections, so a visitor can land where they meant to go without touching navigation.
Two more worth knowing about: error8 counts down and then calls onRedirect, which suits a retired campaign URL you want to move on from automatically, and error10 is the full-screen version with a background photograph for marketing sites where the 404 still has to look like the brand.
When it is not the visitor's fault
A 500 is an apology with a job to do. The visitor cannot fix anything, so the only useful thing the page can give them is something support can act on. error15 puts a reference, a timestamp, the affected service and the current state in a table, and pairs it with a retry button that maps straight onto the reset function from a Next.js error.tsx.
Planned downtime is a different conversation. Nothing is broken, you did this deliberately, and the two questions are how long and how will I know. error17 answers both: three cells for the window and a working notify field for the all clear. Serve it with a 503 and a Retry-After, and both your visitors and your crawlers are told the truth.
Rate limits are the state most often shipped as a raw JSON error. A 429 is genuinely temporary, so the page should say when the limit lifts rather than inviting a refresh loop. error25 counts down from the value you pass, and only when it reaches zero does the retry button appear in its place.
When the failure is the network rather than the server, error31 shows what is still waiting to send on the device and retries on a loop, raising the attempt number each time, so an offline app feels paused rather than broken.
When the door is locked
Permission pages fail in a specific way: they tell people what they cannot do and stop there. Every block in this group is built around the next step instead.
error16 is the 403 for team products. It shows the account that is signed in, offers an account switch for the classic wrong-workspace mistake, lists what actually grants access, and puts a request button next to it.
Expired sign-in links belong to the same family, and they are the highest friction moment in any passwordless flow. error18 prints the expiry timestamp, explains the three reasons a single-use link stops working, and turns the resend button into a confirmation the moment it is pressed, which stops the queue of five links people request when nothing appears to happen. error27 covers the timed-out session, including the line that matters most: the draft was saved.
Two commercial variants round the group out. error26 is the 402 after a failed renewal, pairing the unpaid invoice with a list of everything that stays untouched while the account is read only, and error28 is the 451 for licensing limits, naming the visitor's region and showing which markets are live.
Reassurance is a feature
Lockout pages are where support tickets are born. The three lines that prevent
most of them are the same three every time: what is preserved, what happens
next and who can change it. error26 lists what stays safe during a billing
lockout, error27 confirms the autosave, and error31 states that queued
changes survive a closed tab.
Every value on the screen is a prop
The rule behind this category is the same one behind the dashboard set: if you can read it, you can pass it. Every block renders its full demo with zero props, and each prop is optional, so real content replaces the demo one field at a time.
"use client";
import { Error15 } from "@/components/beste/block/error15";
export default function ErrorPage({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<Error15
label="Server Error"
code="500"
heading="Something broke <strong>on our side</strong>."
description="Your request never reached the finish line. The team has been paged."
labels={{ retry: "Run it again", detailsTitle: "Incident details" }}
details={[
{ title: "Reference", value: error.digest ?? "unavailable" },
{ title: "Service", value: "Checkout API" },
{ title: "Status", value: "Under investigation" },
]}
link={{ label: "Follow the status page", href: "/status" }}
onRetry={reset}
/>
);
}Every block also exports its demo object, error15Demo here, which doubles as documentation: the exact shape your data needs, in a file you can open and edit.
The details you would have had to get right yourself
- No block navigates on its own. Countdowns, searches and retries call
onRedirect,onSearchandonRetry. Routing stays in your code, which is what makes these safe inside anerror.tsxboundary and safe to preview. - Timers announce themselves. The countdowns in
error8,error25anderror31sit inaria-live="polite"regions, so the remaining time reaches a screen reader without stealing focus every second. - The forms are real. Search and notify fields are ordinary
<form>elements with submit handlers and labelled inputs. If yours needs validation on top, our forms deep dive covers the layer these pair with. - Long paths do not break the layout. Blocks that echo a URL truncate or wrap it deliberately, because the pathological case here is a two hundred character query string.
- Theming is token-based. Everything reads from your CSS variables, so the blocks adopt your brand and your dark mode on arrival. If you are still shaping those tokens, the anatomy of a shadcn theme walks through the variables these depend on.
The eleven at a glance
Each block links to a live, full-screen preview on its page. These are the highlights; the Error category holds all thirty-three.
| Block | Status | Best for |
|---|---|---|
| error1 | 404 | The baseline every site needs first |
| error2 | 404 | Sites too large for a link shortlist |
| error9 | 404 | Documentation with moved URLs |
| error11 | 404 | Retired product pages in a store |
| error12 | 404 | Unpublished or renamed articles |
| error13 | 404 | Large sites that need a full directory |
| error15 | 500 | Failures support has to trace |
| error16 | 403 | Permission walls in team products |
| error17 | 503 | Planned maintenance windows |
| error18 | 410 | Expired magic links and invites |
| error25 | 429 | Rate limits with a real cooldown |
The first three are free to install and try. The rest are part of Beste UI Pro, alongside the onboarding, coming soon and landing page sets.
Where these get discovered
We publish the whole catalog as a structured, machine-readable feed so search engines and assistants can index it directly, which is how a query like "shadcn 404 page" surfaces a component instead of a screenshot gallery. Our MCP server exposes the same catalog to coding assistants, so an agent working in your repo can install the right error page without leaving the editor.
Design the page you hope nobody reaches
Error pages are the cheapest trust you will ever buy. A visitor who hits a dead product URL and finds four alternatives keeps shopping. A customer who hits a billing wall and reads exactly what is preserved opens a ticket instead of a chargeback. An engineer who hits a 500 with a reference code files something you can act on. Pick the status code first, pick the block that matches the situation second, and give the page the same care you gave the one that sent them there.