Too-many-requests page with a square eyebrow and status code, an accent headline, three bordered usage cells, and a live cooldown counter that swaps itself for a retry seal button when it reaches zero.
A too-many-requests page that counts itself down: three bordered cells state the usage window, and a live counter holds the place of the retry button until the limit lifts, at which point the seal button appears.
Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.
Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.
Radix flavor
npx shadcn add "https://ui.beste.co/r/error25?email=YOUR_EMAIL&license_key=YOUR_KEY"Base UI flavor
npx shadcn add "https://ui.beste.co/r-base/error25?email=YOUR_EMAIL&license_key=YOUR_KEY"This installs the block to components/beste/block/error25.tsx, plus the two design-system primitives it renders through: badge6 for the square eyebrow and button1 for the retry seal button.
The installed file exports error25Demo alongside the block: the exact props behind the preview above. Spread it and the cooldown starts counting immediately.
import { Error25, error25Demo } from "@/components/beste/block/error25";
export default function RateLimited() {
return <Error25 {...error25Demo} />;
}Feed cooldownSeconds from your Retry-After header and handle the retry yourself:
"use client";
import { Error25 } from "@/components/beste/block/error25";
export default function RateLimited({ retryAfter }: { retryAfter: number }) {
return (
<Error25
label="Rate Limited"
code="429"
heading="You are asking <strong>faster than we answer</strong>."
description="Nothing is broken and nothing was charged. The limit lifts on its own."
labels={{
retry: "Send it again",
waiting: "The limit lifts in",
seconds: "seconds",
usageTitle: "Your window",
}}
cooldownSeconds={retryAfter}
stats={[
{ title: "Requests used", value: "1,000" },
{ title: "Allowed per minute", value: "1,000" },
{ title: "Window", value: "Rolling 60s" },
]}
link={{ label: "Raise your limit", href: "/pricing" }}
onRetry={() => location.reload()}
/>
);
}| Prop | Type | Default | Description |
|---|---|---|---|
label | string | – | Eyebrow text rendered through the square-marker badge |
code | string | – | Status code shown after the eyebrow divider |
heading | string | – | Headline HTML, rendered as an h1; <strong> marks the accent clause |
description | string | – | Explanatory paragraph under the headline |
labels | Error25Labels | {} | Retry text, waiting sentence, unit word, and the caption under the cells |
cooldownSeconds | number | – | Starting value of the cooldown counter |
stats | UsageStat[] | [] | Bordered cells describing the limit |
link | ActionLink | – | Text link beside the counter or button |
onRetry | () => void | – | Click handler for the retry button |
className | string | – | Extra classes for the outer <section> |
type Error25Labels = {
retry?: string;
waiting?: string;
seconds?: string;
usageTitle?: string;
};
type UsageStat = {
title: string;
value: string;
};
type ActionLink = {
label: string;
href: string;
};remaining is above zero the block shows the waiting sentence, and at zero it swaps in the seal button. Nothing is disabled and greyed out, so there is no dead control on screen.cooldownSeconds value.cooldownSeconds (or 0) starts the block in the retry state, which is the right shape when your API does not send a Retry-After value.aria-live="polite", so screen readers hear the remaining time without the page grabbing focus every second.labels.usageTitle renders as a caption underneath the cells, not above them, so the numbers lead and the explanation follows.error15
Server error page with a square eyebrow and status code over a hairline rule, a bold accent headline, a bordered incident table of reference, timestamp and service rows, and a retry seal button beside a status page link.
error17
Planned downtime page with a square eyebrow and status code, a bold accent headline, three bordered cells for the maintenance window, a working notify-me email form with a success state, and a live progress link.
error29
Monochrome 404 led by the status code set as an oversized numeral, with the statement and pill call to action on one side of a hairline rule and the explanation plus display-type links on the other.
error8
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.
error14
Editorial 404 with a square eyebrow and status code over a hairline rule, an oversized accent-highlighted headline, and a ruled index of the sections visitors reach for next.
error21
Monochrome server-error page: a parenthetical eyebrow over a display headline, a retry pill beside a status link, and a ruled two-column record of the reference, timestamp, subsystem and state.