# Beste UI

> Beautiful, accessible blocks, pieces and components for shadcn/ui and Tailwind CSS, built for React and Next.js. Install one with the shadcn CLI and the code is yours to edit: no runtime dependency, nothing to upgrade.

Markdown rendition of https://ui.beste.co/blog/33-shadcn-error-page-blocks. Every page on the site has one: add `.md` to any address, or send `Accept: text/markdown`.

Last updated: 2026-07-23

Whole catalog for LLMs: https://ui.beste.co/llms.txt. Registry index: https://ui.beste.co/r/registry.json

---
[All posts](/blog)

July 23, 2026·13 min read

# 33 shadcn Error Page Blocks: 404, 500, 403 and Every Other Way a Request Can End

A production error page is a status code plus a way forward. This is the new Error category: thirty-three drop-in blocks built on shadcn/ui for 404, 500, 403, 410, 429, 451, 503 and offline states, with a guide to which status code each one belongs to. Eleven of the best, explained by use case.

error404shadcnreactnextjs

![zieg](/_next/image?url=%2Fassets%2Fimages%2Fzieg.jpg&w=96&q=75&dpl=dpl_CY2dAqud5esqh3XyqpW5XSreDKej)

ziegbuilding beste.co

[zieg's website](https://zieg.beste.co)[zieg on X](https://x.com/forwardset)[zieg on LinkedIn](https://linkedin.com/in/ziegfiroyt)

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](/blocks/error) 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:

bashCopy

```
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](/blog/base-ui-the-new-default) of the same component, and the [installation docs](/docs/installation) 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](/block/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.

Centered 404 Message

[View block](/block/error1)

Loading preview…

error1: the baseline 404\. An oversized code, a heading, an explanation, two buttons and a row of popular links. Free to install.

Once a site is large enough that no shortlist of links can cover it, search beats guessing. [error2](/block/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.

404 With Site Search

[View block](/block/error2)

Loading preview…

error2: search-first. A controlled field passes the trimmed query to onSearch, above a list of suggested destinations. Free to install.

Documentation sites have a narrower problem: the page did not vanish, it moved. [error9](/block/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.

404 Did You Mean

[View block](/block/error9)

Loading preview…

error9: did you mean. The requested path is echoed struck through, with the nearest matching page linked below it. Free to install.

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](/block/error11) keeps the intent alive by putting four products under the apology.

404 With Product Recommendations

[View block](/block/error11)

Loading preview…

error11: the retired product URL. Shopping actions above a four-up recommendation grid with prices and optional compare-at prices.

Publishers get the same shape with different content. [error12](/block/error12) fills the space under a dead post URL with the three articles people are actually reading, and [error32](/block/error32) does it in a quieter editorial layout for a renamed essay. For a site too large for either, [error13](/block/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.

404 With Site Directory

[View block](/block/error13)

Loading preview…

error13: the 404 as a sitemap. Four columns of grouped section links for sites where a shortlist of suggestions cannot cover the surface.

Two more worth knowing about: [error8](/block/error8) counts down and then calls `onRedirect`, which suits a retired campaign URL you want to move on from automatically, and [error10](/block/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](/block/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`.

500 Incident Report

[View block](/block/error15)

Loading preview…

error15: the 500 as an incident report. A reference, timestamp, service and state in a bordered table, with a retry button wired to onRetry.

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](/block/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.

Maintenance Window Notice

[View block](/block/error17)

Loading preview…

error17: the maintenance window. Start, expected return and affected services in bordered cells, above a notify-me field with a success state.

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](/block/error25) counts down from the value you pass, and only when it reaches zero does the retry button appear in its place.

Rate Limit Cooldown

[View block](/block/error25)

Loading preview…

error25: the rate limit cooldown. A live counter holds the slot until the limit lifts, then swaps itself for the retry button. Feed it your Retry-After value.

When the failure is the network rather than the server, [error31](/block/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](/block/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.

403 Access Request

[View block](/block/error16)

Loading preview…

error16: the 403 for team tools. The signed-in account, an account switch link, the requirements that unlock the page and a request action.

Expired sign-in links belong to the same family, and they are the highest friction moment in any passwordless flow. [error18](/block/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](/block/error27) covers the timed-out session, including the line that matters most: the draft was saved.

Two commercial variants round the group out. [error26](/block/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](/block/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](/blog/13-shadcn-ui-dashboard-blocks): 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.

tsxCopy

```
"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`, `onSearch` and `onRetry`. Routing stays in your code, which is what makes these safe inside an `error.tsx` boundary and safe to preview.
* **Timers announce themselves.** The countdowns in `error8`, `error25` and `error31` sit in `aria-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](/blog/shadcn-forms-react-hook-form-tanstack-formisch) 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](/blog/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](/blocks/error) holds all thirty-three.

| Block                     | Status | Best for                               |
| ------------------------- | ------ | -------------------------------------- |
| [error1](/block/error1)   | 404    | The baseline every site needs first    |
| [error2](/block/error2)   | 404    | Sites too large for a link shortlist   |
| [error9](/block/error9)   | 404    | Documentation with moved URLs          |
| [error11](/block/error11) | 404    | Retired product pages in a store       |
| [error12](/block/error12) | 404    | Unpublished or renamed articles        |
| [error13](/block/error13) | 404    | Large sites that need a full directory |
| [error15](/block/error15) | 500    | Failures support has to trace          |
| [error16](/block/error16) | 403    | Permission walls in team products      |
| [error17](/block/error17) | 503    | Planned maintenance windows            |
| [error18](/block/error18) | 410    | Expired magic links and invites        |
| [error25](/block/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](/blog/32-shadcn-onboarding-blocks), [coming soon](/blog/75-shadcn-coming-soon-blocks) and [landing page](/blog/landing-page-in-ten-blocks) 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](/blog/beste-ui-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.

Cover every state, not just the 404

All thirty-three error blocks and every other Pro block are included in one license. Install them from the CLI and own the code.

[Get ProGet Pro](/pricing)

## Build it with real blocks

Every section in this post is a component you can copy into your own codebase and own outright.

[Browse blocksBrowse blocks](/blocks)[Read more postsRead more posts](/blog)

## Ship the interface, keep the code.

New blocks, pieces and components every week. Install one with a command and it is yours to edit. No runtime dependency, no upgrade path to fight.

[Browse the libraryBrowse the library](/blocks)[See pricingSee pricing](/pricing)

### Library

* [Blocks](/blocks)
* [Pages](/pages)
* [Pieces](/pieces)
* [Components](/components)
* [Search](/search)

### Learn

* [Docs](/docs)
* [AI & MCP](/docs/mcp)
* [Blog](/blog)
* [Free tools](/tools)
* [What's new?](/changelog)
* [Website Builder](https://beste.co)

### More

* [Pricing](/pricing)
* [Referrals](/referrals)
* [License](/license)
* [GitHub](https://github.com/beste-co/beste-ui)

### Shadcn Blocks

* [Shadcn Hero Blocks](/blocks/hero)
* [Shadcn Feature Blocks](/blocks/feature)
* [Shadcn Pricing Blocks](/blocks/pricing)
* [Shadcn CTA Blocks](/blocks/cta)
* [Shadcn FAQ Blocks](/blocks/faq)
* [Shadcn About Blocks](/blocks/about)
* [Shadcn Stats Blocks](/blocks/stats)
* [Shadcn Footer Blocks](/blocks/footer)
* [Shadcn Navigation Blocks](/blocks/navigation)
* [Shadcn Auth Blocks](/blocks/auth)
* [Shadcn Ecommerce Blocks](/blocks/ecommerce)
* [Shadcn Portfolio Blocks](/blocks/portfolio)
* [Shadcn Showcase Blocks](/blocks/showcase)
* [Shadcn Careers Blocks](/blocks/careers)
* [Shadcn Onboarding Blocks](/blocks/onboarding)
* [Shadcn Coming Soon Blocks](/blocks/coming-soon)
* [Shadcn Post Blocks](/blocks/post)
* [Shadcn Legal Blocks](/blocks/legal)
* [Shadcn Workflow Blocks](/blocks/workflow)
* [Shadcn News Blocks](/blocks/news)

© 2026, [Beste](https://beste.co). All rights reserved.