API Keys Page React block for Tailwind CSS v4
v1.0.0Added in 1.10.0
The developer settings screen for API keys: list, create with scopes and expiry, reveal the secret once, copy, roll, rename and revoke.
The header names the page, counts the active keys and holds API docs and Create key. Each key shows its name and creator, the masked prefix (ak_live_3f9a••••), status, scopes, created date, when it was last used and when it expires; a key expiring within a week turns amber with "Expires in 5 days". Expired and revoked keys are muted (an active key past its date shows as expired too), and expired ones offer Renew, which opens the create dialog prefilled and replaces the expired row. The ... menu copies the key prefix, renames, rolls or revokes (rolling and revoking ask first, revoking in red). Create key opens a dialog with the cursor in Name: pick scopes and an expiry (the date it lands on shows under it) and press Enter or Create key. Missing fields are named inline, a failed request keeps the form with the error. Then the same dialog shows the secret once in a read-only field, focused and selected, with Copy and a warning; Done turns on after you copy it or tick "I saved this key somewhere safe", and until then outside clicks and Escape do not close it. The secret never goes into a toast, the URL or storage. At the plan's key limit Create key turns off and a hint says why. On phones the keys become cards with the menu as a bottom sheet, and the dialog is a bottom sheet with full-width buttons.
API Keys Page is a Pro React block in the React blocks category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add api-keys-page, together with Alert, Alert Dialog, Badge, Button, Checkbox and 13 more and the npm packages motion and lucide-react.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add api-keys-pagePro item: the CLI needs a Pro account. See plans
Usage
Import ApiKeysPage from @/components/blocks/api-keys-page.
"use client"
import { ApiKeysPage } from "@/components/blocks/api-keys-page"
import type { ApiKey } from "@/components/blocks/api-keys-page-data"
export function DeveloperKeys({ keys }: { keys: ApiKey[] }) {
return (
<ApiKeysPage
defaultKeys={keys}
docsHref="https://docs.acme.com/api"
maxKeys={10}
onCreate={async (input) => {
const res = await fetch("/api/keys", { method: "POST", body: JSON.stringify(input) })
if (!res.ok) throw new Error("The key was not created. Try again.")
return res.json() as Promise<{ key: ApiKey; secret: string }>
}}
onRoll={async (id) => {
const res = await fetch(`/api/keys/${id}/roll`, { method: "POST" })
return res.json() as Promise<{ secret: string }>
}}
onRevoke={(id) => fetch(`/api/keys/${id}`, { method: "DELETE" })}
/>
)
}Props
titledefault"API keys"ReactNodeThe page title.
descriptiondefault"Use these keys to call the API from your servers. Never share them in client code."ReactNodeThe line under the title.
docsHrefdefault""stringAdds an API docs link to the header. Empty hides it.
keysApiKey[]Controlled list: { id, name, prefix, createdAt, lastUsedAt?, expiresAt?, scopes, createdBy?, status: "active" | "expired" | "revoked" }. Pair it with onKeysChange.
defaultKeysdefault"API_KEYS_SAMPLE"ApiKey[]Starting list while uncontrolled. The sample has an active key, one expiring in 5 days, an expired and a revoked one.
onKeysChange(keys: ApiKey[]) => voidThe list after a create, roll, rename or revoke.
scopesdefault"DEFAULT_API_KEY_SCOPES"ApiKeyScope[]The scope options { value, label, description? }: checkboxes in the create dialog and badges in the list. Default Read, Write and Admin.
expiryOptionsdefault"DEFAULT_API_KEY_EXPIRY_OPTIONS"ApiKeyExpiryOption[]The expiry choices { value, label, days } where days null means never. Default 30 days, 90 days, 1 year and Never.
labelsPartial<ApiKeysPageLabels>Merged over DEFAULT_API_KEYS_PAGE_LABELS: every string of the page, the dialogs and the toasts.
defaultExpirydefault"90d"stringThe expiry preselected in the create dialog.
maxKeysdefault0numberActive keys allowed. At the limit Create key turns off and an info hint explains it. 0 means no limit.
warnDaysdefault7numberAn active key expiring within this many days shows an amber "Expires in ...".
onCreate(input: { name, scopes, expiresIn, renews? }) => Promise<{ key: ApiKey; secret: string }>Create the key on your server and return it with its secret, which the dialog shows once. renews is the id of the expired key a Renew replaces. A rejection keeps the form and shows its message. Without it the page makes a local sample key so the flow can be tried.
onRevoke(id: string) => void | Promise<unknown>Revoke was confirmed. The row turns Revoked once it resolves; a rejection shows in the confirmation.
onRoll(id: string) => Promise<{ secret: string }>Adds Roll key to active rows: after a confirmation the new secret is revealed once, like a new key.
onRename(id: string, name: string) => void | Promise<unknown>Adds Rename to the row menu, with a small dialog.
defaultCreateOpendefaultfalsebooleanOpens the create dialog on mount, e.g. from an onboarding link.
nowDate | numberThe reference time for last used and expiry warnings; default the live clock (dates show until it hydrates).
localedefault""stringIntl locale for dates and relative times. Empty falls back to the UIProvider locale, then en-US (the demo offers en-GB and de-DE).
classNamestringClasses for the root, a centered column up to 1152px wide. Other div props go to the root too.
classNamesPartial<Record<ApiKeysPageSlot, string>>Classes for header, limit, table, empty, dialog and reveal.
Parts
DEFAULT_API_KEYS_PAGE_LABELS
constantThe English strings; spread it and override what you need for another language.
DEFAULT_API_KEY_SCOPES
constantRead, Write and Admin, each with a one-line description.
DEFAULT_API_KEY_EXPIRY_OPTIONS
constant30 days, 90 days, 1 year and Never.
API_KEYS_SAMPLE
constantFour sample keys (api-keys-page-data.ts): active, expiring in 5 days, expired and revoked.
API_KEYS_NOW
constantThe reference time the sample is written against (Oct 13, 2026, 10:30); pass it as now in demos and tests.
generateDemoApiKey
functionReturns a made-up { prefix, secret } for demos and tests. Real keys come from your server.
ApiKey
typeOne key: { id, name, prefix, createdAt, lastUsedAt?, expiresAt?, scopes, createdBy?, status }.
ApiKeyStatus
type"active" | "expired" | "revoked".
ApiKeyScope
type{ value, label, description? }: one scope option.
ApiKeyExpiryOption
type{ value, label, days }: one expiry choice; days null never expires.
ApiKeyCreateInput
typeWhat onCreate receives: { name, scopes, expiresIn, renews? }, expiresIn being the expiry option's value and renews the id of the expired key being renewed.
ApiKeyCreateResult
typeWhat onCreate returns: { key, secret }.
ApiKeysPageLabels
typeThe shape of labels.
Examples
No keys yet
defaultKeys={[]}: the empty state with Create key and a docs link.
Create a key
defaultCreateOpen: name, scopes and expiry, then the one-time reveal with Copy and Done.
At the key limit
maxKeys={3} with three active keys: Create key turns off and a hint says why.
Create fails
The first request fails: the form keeps its values and shows the error. Try again and the key appears.
en-GB dates, 14-day warning
locale="en-GB", warnDays={14}, custom copy, with Rename and Roll in the row menu.
Dependencies
Files
- components/blocks/api-keys-page.tsx
- components/blocks/api-keys-page-data.ts
FAQ
Is the React API Keys Page block free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install API Keys Page?
Run npx wingo-ui@latest add api-keys-page. It also adds the Wingo UI items it builds on (Alert, Alert Dialog, Badge, Button, Checkbox and 13 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/api-keys-page.
How does API Keys Page behave on phones?
On phones the keys become cards with the menu as a bottom sheet, and the dialog is a bottom sheet with full-width buttons.