Access Denied Screen
A copy-paste React denial screen for a hard-locked route or a custom Protected Route fallback — no access-code form, no retry. Zero dependencies.
Best used for A hard-denial state after too many failed attempts, or a custom unauthorizedFallback on Protected Route.
Distinct from the access-code entry UI <KnockCodes> renders while locked — this screen means "no more attempts,"
not "try again."
Minimal setup
<ProtectedRoute
expectedHash={process.env.NEXT_PUBLIC_KNOCK_HASH}
unauthorizedFallback={<AccessDeniedScreen action={<a href="/contact">Contact us</a>} />}
>
<Dashboard />
</ProtectedRoute>
Access denied
You don't have permission to view this page.
- components
- knock-codes
- react
import type { ReactNode } from "react";
import { GateWrapper, type GateWrapperVariant } from "./GateWrapper.tsx";
import { cx } from "./cx.ts";
export interface AccessDeniedScreenProps {
heading?: string;
message?: ReactNode;
action?: ReactNode;
variant?: GateWrapperVariant;
className?: string;
}
/**
* A static "you don't have access" view — distinct from the PIN entry UI
* `<KnockCodes>` renders while locked, because it never accepts another
* attempt. Use it as a custom `unauthorizedFallback` (e.g. on
* `<ProtectedRoute>`) or anywhere a route/component needs a denial state
* with no verification form in it at all.
*/
export function AccessDeniedScreen({
heading = "Access denied",
message = "You don't have permission to view this page.",
action,
variant = "page",
className,
}: AccessDeniedScreenProps) {
return (
<GateWrapper variant={variant} className={className}>
<div className={cx("w-full max-w-sm space-y-3 text-center")}>
<h1 className="text-lg font-semibold text-gray-900 dark:text-gray-50">{heading}</h1>
<p className="text-sm text-gray-600 dark:text-gray-400">{message}</p>
{action}
</div>
</GateWrapper>
);
}
Add this block to your project
Recommended
npx shadcn@latest add @knock-codes/access-denied-screenAlso installs
- cx (classname helper)
- Gate Wrapper
These install together as one atomic unit — even a presentational or read-only piece needs the full verification stack (hook, types, core) behind it to actually run.
Files created (3)
- components/knock-codes/react/cx.ts
- components/knock-codes/react/GateWrapper.tsx
- components/knock-codes/react/AccessDeniedScreen.tsx
Other ways
GitHub shorthand
npx shadcn@latest add trivedi-vatsal/knock-codes/access-denied-screenCopy the files by hand
- Open the Code tab in the preview above.
- Create each path listed below in your project and paste its contents in.
- Do the same for anything listed under “Also installs”, if present.
API reference
| Prop | Type | Default | Description |
|---|---|---|---|
| heading | string | "Access denied" | The main heading. |
| message | ReactNode | "You don’t have permission to view this page." | Body copy under the heading. |
| action | ReactNode | — | Optional call to action under the message. |
| variant | "page" | "inline" | "page" | Outer positioning. |
Accessibility
A plain heading + message + optional action — no live region needed since nothing here changes without a full re-render triggered by the parent.
Customization
Not wired to any verification strategy on purpose. Use it as a custom `unauthorizedFallback` on `<ProtectedRoute>`, or anywhere you need a denial state that never accepts another attempt — pass `action` for a "contact us" link or a retry button that resets your own flow.
Need a hash? Use the hash generator on Getting Started — computed locally, never sent anywhere.
The honest version
Knock Codes stops casual visitors, search engines, and forwarded links. Local mode does not stop anyone who opens DevTools — the hash ships in your client bundle by design. Server mode (swap one prop) hides the hash from the client; children you already bundled are still in the JavaScript, and a forged session works unless you wire validateSession. A velvet rope, with an optional real lock. Never marketed as more than that.
Blocks that pair well with this one
These combine naturally with this block, whether as a shared shell, a shared session, or a common fallback.
- Protected RouteA copy-paste React access screen for route-level guarding — React Router or similar, with an optional custom denial state. Zero dependencies.
- Gate WrapperA copy-paste React layout primitive for composing a fully custom access-code UI — page-centered or inline positioning, no verification logic. Zero dependencies.