caliper uidocs

Error template

Block

The 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.

Installs components/ui/error-template.tsxDepends on @radix-ui/react-slot, lucide-reactMotion shared motion tokens
pnpm dlx shadcn@latest add https://caliperui.site/r/error-template.json
Skip to content

Page 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 #

terminal
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.

example.tsx
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
PropTypeDefaultDescription
kind *ErrorKind—
brand *string—
navreadonly BlockNavLink[]—
signInBlockAction—
primaryBlockAction—
titlestring—
leadstring—
actions{ primary?: BlockAction | undefined; secondary?: BlockAction | undefined; }—Usually "Go home" and a quiet "Go back".
suggestionsreadonly { label: string; href: string; description?: string | undefined; }[][]Places people usually wanted, for 404.
onSearch((query: string) => void)—404: adds a search; called with the query.
signedInAsstring—403: the account that was refused, e.g. "dana@forma.studio".
onRequestAccess((note: string) => void | Promise<void>)—403: asks the owner. May return a promise; a thrown error is shown.
referencestring—500: an ID support can look up.
onRetry(() => void | Promise<void>)—500, 503 and offline: try the page again.
statusHrefstring—500 and 503: the public status page.
untilstring—503: when maintenance is expected to end, as an ISO date-time.
onResend(() => void | Promise<void>)—expired: sends a new link.
isOnlineboolean—offline: override the browser's online state, e.g. in a preview.
footerTemplateFooter—
classNamestring—

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.