Session Timeout Banner
A copy-paste React warning banner for Knock Codes access screens — alerts before a shared session expires, with a one-click logout. Zero dependencies.
Best used for Warning users before a shared session expires, especially with a short or activity-based timeout.
Requires a <KnockCodesProvider> ancestor — it watches a session something else already established, it
doesn't create one.
Minimal setup
<KnockCodesProvider expectedHash={hash} timeout={1_800_000}>
<KnockCodes>
<Dashboard />
<SessionTimeoutBanner warnBeforeMs={60_000} />
</KnockCodes>
</KnockCodesProvider>
Demo code: 4242 — timeout set to 12s
- components
- knock-codes
- core
- react
"use client";
import { useEffect, useState } from "react";
import { useKnockCodesContext } from "./KnockCodesProvider.tsx";
import { EXPIRY_POLL_INTERVAL_MS } from "./types.ts";
import { cx } from "./cx.ts";
export interface SessionTimeoutBannerProps {
/** Show the banner once this many ms remain before expiry. @default 60_000 */
warnBeforeMs?: number;
/** Switches to the stronger "about to expire" styling once this many ms remain. @default 10_000 */
criticalBeforeMs?: number;
className?: string;
}
/**
* Warns before the shared session (from `<KnockCodesProvider>`) expires,
* with a one-click way to log out immediately instead of waiting for the
* automatic expiry. Renders nothing outside the warning window or with no
* active session — requires a provider, since a banner needs a session to
* watch that something else already established.
*/
export function SessionTimeoutBanner({ warnBeforeMs = 60_000, criticalBeforeMs = 10_000, className }: SessionTimeoutBannerProps) {
const { session, logout } = useKnockCodesContext();
const [now, setNow] = useState(() => Date.now());
useEffect(() => {
if (!session) return;
const interval = setInterval(() => setNow(Date.now()), EXPIRY_POLL_INTERVAL_MS);
return () => clearInterval(interval);
}, [session]);
if (!session) return null;
const remainingMs = session.expiresAt - now;
if (remainingMs > warnBeforeMs || remainingMs <= 0) return null;
const remainingSeconds = Math.max(0, Math.ceil(remainingMs / 1000));
const critical = remainingMs <= criticalBeforeMs;
return (
<div
role="alert"
style={{ fontFamily: "var(--ag-font, inherit)" }}
className={cx(
"flex items-center justify-between gap-4 rounded-[var(--ag-radius,0.5rem)] border px-4 py-2.5 text-sm",
critical
? "border-red-500/40 bg-red-500/10 text-red-900 dark:border-red-400/40 dark:bg-red-400/10 dark:text-red-200"
: "border-amber-500/40 bg-amber-500/10 text-amber-900 dark:border-amber-400/40 dark:bg-amber-400/10 dark:text-amber-200",
className
)}
>
<span className="inline-flex items-center gap-2 font-medium">
<span
aria-hidden="true"
className={cx(
"h-1.5 w-1.5 shrink-0 rounded-full",
critical ? "animate-pulse bg-red-500 dark:bg-red-400" : "bg-amber-500 dark:bg-amber-400"
)}
/>
{critical ? "Expiring now" : "Expires soon"} — session ends in {remainingSeconds}s.
</span>
<button type="button" onClick={logout} className="font-medium underline underline-offset-2 hover:no-underline">
Log out now
</button>
</div>
);
}
Add this block to your project
Recommended
npx shadcn@latest add @knock-codes/session-timeout-bannerAlso installs
- Knock Codes Core
- Knock Codes Types
- useKnockCodes
- Session Provider
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 (9)
- components/knock-codes/core/hash.ts
- components/knock-codes/core/verify.ts
- components/knock-codes/core/session.ts
- components/knock-codes/core/storage.ts
- components/knock-codes/react/types.ts
- components/knock-codes/react/useKnockCodes.ts
- components/knock-codes/react/KnockCodesContext.tsx
- components/knock-codes/react/KnockCodesProvider.tsx
- components/knock-codes/react/SessionTimeoutBanner.tsx
Other ways
GitHub shorthand
npx shadcn@latest add trivedi-vatsal/knock-codes/session-timeout-bannerCopy 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 |
|---|---|---|---|
| warnBeforeMs | number | 60000 | Show the banner once this many ms remain before expiry. |
| className | string | — | Extra classes on the banner. |
Accessibility
role="alert" — announced immediately when it appears, not just on next poll of an aria-live region, since an imminent session expiry is time-sensitive.
Customization
Renders nothing outside the warning window or with no active session, so it's safe to mount unconditionally near the top of a layout. Adjust `warnBeforeMs` to change how early it appears.
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.
- Session ProviderA copy-paste React session provider for Knock Codes access screens — shares one unlock state across a gate, a logout button, and a timeout banner. Zero dependencies.
- Logout ButtonA copy-paste React logout control for Knock Codes access screens — clears the shared session on click. Zero dependencies.
- Access ReceiptA copy-paste React session receipt for Knock Codes access screens — unlock time, storage mode, timeout. Zero dependencies.