# 404 With Redirect Countdown

> 404 page that counts down to an automatic redirect and calls onRedirect once it reaches zero, with a cancel button that stops the timer and a manual go-now link.

A shadcn/ui block from the [@beste-ui](https://ui.beste.co) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/beste-ui/error8).

- Page, with a live preview: https://shadcn.exchange/r/beste-ui/error8
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/beste-ui/error8-light-eef0bd90f36d4769.png
- Registry item: https://ui.beste.co/r/error8.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.beste.co/r/error8.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `clsx`, `lucide-react`, `tailwind-merge`
- Registry items: `button`

## Files

### `registry/error8/error8.tsx`

```tsx
"use client";

import Link from "next/link";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";

interface ActionLink {
  label: string;
  href: string;
}

interface Error8Labels {
  redirect?: string;
  seconds?: string;
  cancel?: string;
  canceled?: string;
}

interface Error8Props {
  code?: string;
  heading?: string;
  description?: string;
  seconds?: number;
  labels?: Error8Labels;
  action?: ActionLink;
  onRedirect?: () => void;
  className?: string;
}

export const error8Demo: Error8Props = {
  code: "404",
  heading: "This page does not exist",
  description:
    "Nothing to see here. We will take you somewhere useful unless you would rather stay.",
  seconds: 10,
  labels: {
    redirect: "Redirecting to the homepage in",
    seconds: "seconds",
    cancel: "Stay on this page",
    canceled: "Automatic redirect canceled.",
  },
  action: {
    label: "Go now",
    href: "https://beste.co",
  },
};

export function Error8({
  code,
  heading,
  description,
  seconds,
  labels = {},
  action,
  onRedirect,
  className,
}: Error8Props) {
  const {
    redirect: redirectLabel,
    seconds: secondsLabel,
    cancel: cancelLabel,
    canceled: canceledLabel,
  } = labels;

  const [remaining, setRemaining] = React.useState(seconds ?? 0);
  const [canceled, setCanceled] = React.useState(false);
  const hasFired = React.useRef(false);

  React.useEffect(() => {
    if (canceled || remaining <= 0) return;
    const timer = setTimeout(() => setRemaining((value) => value - 1), 1000);
    return () => clearTimeout(timer);
  }, [canceled, remaining]);

  React.useEffect(() => {
    if (!seconds || canceled || remaining > 0 || hasFired.current) return;
    hasFired.current = true;
    onRedirect?.();
  }, [seconds, canceled, remaining, onRedirect]);

  return (
    <section className={cn("py-16 md:py-24 w-full", className)}>
      <div className="mx-auto max-w-xl px-4 text-center md:px-6">
        {code && (
          <p className="text-sm font-medium uppercase tracking-wider text-muted-foreground">
            {code}
          </p>
        )}

        {heading && (
          <h1 className="mt-4 text-balance text-3xl font-bold tracking-tight md:text-4xl">
            {heading}
          </h1>
        )}

        {description && (
          <p className="mt-4 text-balance text-base text-muted-foreground md:text-lg">
            {description}
          </p>
        )}

        <p aria-live="polite" className="mt-8 text-base text-muted-foreground">
          {canceled ? (
            canceledLabel
          ) : (
            <>
              {redirectLabel}{" "}
              <span className="font-semibold text-foreground">{remaining}</span>{" "}
              {secondsLabel}
            </>
          )}
        </p>

        <div className="mt-6 flex flex-wrap justify-center gap-3">
          {action && (
            <Button size="lg" asChild>
              <Link href={action.href}>{action.label}</Link>
            </Button>
          )}
          {cancelLabel && !canceled && (
            <Button
              type="button"
              variant="outline"
              size="lg"
              onClick={() => setCanceled(true)}
            >
              {cancelLabel}
            </Button>
          )}
        </div>
      </div>
    </section>
  );
}
```
