Skip to content
Neon UI
Esc
navigateopen⌘Jpreview
On this page

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

  1. New key opens an inline name field with a personal/organization scope toggle.
  2. On create, onCreate returns the full token, shown once in a highlighted panel with a copy and a plain warning: you will not see it again.
  3. 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

PropType
keysApiKey[]

The keys to list, in display order. Each is { id, name, scope, createdAt, lastUsedAt? } — no token.

TypeApiKey[]
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.

Type(name: string, scope: ApiKeyScope) => Promise<string> | string
onRevoke?(key: ApiKey) => Promise<void> | void

Revoke a key after the hold-to-confirm. Receives the whole key so the scope is known.

Type(key: ApiKey) => Promise<void> | void
label?string

Card title.

Typestring
Default"API keys"
isLoading?boolean

Render the skeleton state.

Typeboolean
Defaultfalse
error?Error | string | null

Render the error state with the message.

TypeError | string | null
Defaultnull

Accessibility

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".