ApiKeyList
Create, reveal-once, and revoke Neon API keys (personal or organization), with an inline create flow, a one-time secret reveal, a scope filter, and hold-to-revoke.
ApiKeyList manages a set of Neon API keys, personal or organization, in a table of rows: each shows a scope icon, the name, its scope, and when it was created and last used. Create a key inline, copy the token the one time it is shown, filter by scope, and revoke any key. It is presentational and honest about secrets: the token only ever exists at creation. The list holds names, scopes, and timestamps, never the key.
API keys
production-deploy
Personal
created Jul 18, 2026
used 2h ago
ci-pipeline
Organization
created Jun 30, 2026
used 3d ago
local-scratch
Personal
created May 2, 2026
never used
The reveal-once flow
- New key opens an inline name field with a personal/organization scope toggle.
- On create,
onCreatereturns the full token, shown once in a highlighted panel with a copy and a plain warning: you will not see it again. - Done dismisses the panel; the key joins the list with only its name, scope, and timestamps.
Revoke is a hold-to-confirm: the row’s trash icon opens a Cancel and a Hold to revoke that slide in over the timestamps; a destructive fill sweeps as you press and hold, and releasing early rewinds it. When keys of both scopes are present, a scope filter appears in the header. The panel height animates as the form, reveal, and rows change.
Install
npx shadcn@latest add https://ui.neon.com/r/api-key-list.json
Usage
import { ApiKeyList } from "@/components/neon-ui/api-key-list";
<ApiKeyList
keys={keys}
onCreate={async (name, scope) => {
const token = await createKey(name, scope); // full token, once
return token;
}}
onRevoke={(key) => revokeKey(key)}
/>;
Wiring to Neon
List the account’s keys in a server component and wire create and revoke as server actions:
import { raw } from "@neon/sdk";
import { createNeonClient } from "@/lib/neon-client";
const createKey = async (name: string, scope: ApiKeyScope) => {
"use server";
const neon = createNeonClient(process.env.NEON_API_KEY);
// Organization keys are not in the SDK's ergonomic layer yet, so that one
// call drops to the raw layer, reusing the client's auth.
const { data } =
scope === "organization"
? await raw.createOrgApiKey({
body: { key_name: name },
client: neon.client,
path: { org_id: orgId },
})
: await neon.apiKeys.create(name);
return data?.key; // shown once
};
Props
keysApiKey[]
The keys to list, in display order. Each is { id, name, scope, createdAt, lastUsedAt? } — no token.
ApiKey[]onCreate?(name: string, scope: ApiKeyScope) => Promise<string> | string
Create a key of the chosen scope and return the full token to reveal once. The create UI hides when omitted.
(name: string, scope: ApiKeyScope) => Promise<string> | stringonRevoke?(key: ApiKey) => Promise<void> | void
Revoke a key after the hold-to-confirm. Receives the whole key so the scope is known.
(key: ApiKey) => Promise<void> | voidlabel?string
Card title.
string"API keys"isLoading?boolean
Render the skeleton state.
booleanfalseerror?Error | string | null
Render the error state with the message.
Error | string | nullnullAccessibility
The create field is a real form; revoke is a hold-to-confirm (pointer or keyboard) that fires only after the hold completes; every icon action carries a tooltip and an aria-label; and copy announces through a polite live region. Motion honors reduced-motion. Loading sets aria-busy; the error state is a role="alert".