Error template
BlockThe pages people land on when something goes wrong, each with the way out that fits: search, ask for access, retry with a reference, wait out maintenance, reconnect or get a new link.
pnpm dlx shadcn@latest add https://caliperui.site/r/error-template.jsonPage not found
That page isn't here
The link may be old, or the page may have moved. Search, or start from one of these. Error 404.
When to use #
Use it for
- Your site's 404, 403, 500 and maintenance pages.
- Offline and expired-link screens in a web app.
Skip it when
- Errors inside a form or panel; show them where they happened.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/error-template.json
Adds its 13 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above.
import { useState } from "react" import { ErrorTemplate, type ErrorKind } from "@/components/ui/error-template" import { SegmentedControl } from "@/components/ui/segmented-control" const nav = (current: string) => [ { label: "Product", href: "#product" }, { label: "Customers", href: "#customers" }, { label: "Blog", href: "#blog" }, { label: "About", href: "#about" }, { label: "Contact", href: "#contact" }, ].map((link) => ({ ...link, current: link.label.toLowerCase() === current })); const signIn = { label: "Sign in", href: "#" }; const frame = "h-[720px] w-full overflow-auto rounded-[14px] shadow-[0_0_0_1px_var(--border)]"; const wait = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)); const kinds: { value: ErrorKind; label: string }[] = [ { value: "404", label: "404" }, { value: "403", label: "403" }, { value: "500", label: "500" }, { value: "503", label: "503" }, { value: "offline", label: "Offline" }, { value: "expired", label: "Expired" }, ]; export function ErrorPagesDemo() { const [kind, setKind] = useState<ErrorKind>("404"); const [online, setOnline] = useState(false); const [until] = useState(() => new Date(Date.now() + 25 * 60_000).toISOString()); const [searched, setSearched] = useState(""); return ( <div className="grid w-full gap-3"> <div className="flex flex-wrap items-center gap-3"> <div className="max-w-full overflow-x-auto [scrollbar-width:none]"> <SegmentedControl label="Error page" value={kind} onValueChange={(value) => setKind(value as ErrorKind)} options={kinds} /> </div> {kind === "offline" && ( <button type="button" className="text-[13px] underline underline-offset-2" onClick={() => setOnline((value) => !value)}> {online ? "Go offline again" : "Simulate reconnecting"} </button> )} {searched && kind === "404" && <span role="status" className="text-[13px] text-muted-foreground">Searched for “{searched}”</span>} </div> <div className={frame}> <ErrorTemplate key={kind} kind={kind} brand="Halden" nav={nav("")} signIn={signIn} primary={heroes.primary!} actions={{ primary: { label: "Go to the home page", href: "#" }, secondary: { label: "Go back", href: "#" } }} suggestions={kind === "404" ? [ { label: "Pricing", href: "#pricing", description: "Plans for teams of every size" }, { label: "Changelog", href: "#changelog", description: "What shipped this week" }, { label: "Help centre", href: "#help", description: "Guides and answers" }, ] : []} onSearch={setSearched} signedInAs="dana@forma.studio" onRequestAccess={() => wait(700)} reference="ERR-7Q2K-91XD" onRetry={async () => { await wait(700); if (kind === "500") throw new Error("still down"); }} statusHref="#status" until={until} onResend={() => wait(600)} isOnline={online} footer={footers} /> </div> </div> ); }
API reference #
ErrorTemplate
component
Accessibility #
It exposes status and search semantics, with aria-hidden, aria-label and aria-labelledby kept in step with state. Changes that matter are announced through a live region.
No custom key handling; Tab and the native activation keys do the work.
Motion #
Transitions use the shared duration and easing tokens. Under prefers-reduced-motion those tokens collapse to instant and travel distances go to zero, so state changes still happen without movement.