More Dashboards Snippets
API Key Manager — Free HTML CSS JS Snippet
API Key Manager · Dashboards · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
API Key Manager — Show/Hide Toggle, Copy, Revoke, and Create New Keys

An API key manager is a standard component in any developer-facing SaaS product, authentication service, or platform dashboard. It lets users create, view, copy, and revoke API access tokens from one place. This snippet provides a complete API key manager with masked key display, show/hide toggle with eye icon, copy to clipboard with checkmark feedback, revoke with confirmation dialog, create new key prompt, environment chips (Live/Test), permission badges (read/write/admin), and an empty state when all keys are revoked.
The masked key display
Each key is stored in data-key on the .key-row — never in the visible DOM by default. The .key-code element shows a masked version: the first 8 characters + bullet characters + the last 4 characters. This is the industry-standard partial masking pattern used by Stripe, Vercel, and AWS. toggleKey() reads the full key from data-key and alternates between the masked and full string.
The show/hide toggle
toggleKey() swaps the eye-off and eye-on SVG icons by toggling their display properties. code.dataset.shown tracks the current state (false = masked). The full key is always available in the row's data-key attribute — it never leaves the DOM element, so no server round-trip is needed to reveal it. In production, you would NOT store the full key in the DOM — instead, fetch it from the API on demand.
Copy to clipboard
copyKey() uses navigator.clipboard.writeText() to copy the full key. On success, the copy icon morphs to a checkmark (by replacing the SVG innerHTML) and .copied class adds a green tint. After 2 seconds, the icon reverts. This pattern is identical to the copy button snippet but embedded in the key row.
Revoke and empty state
revokeKey() shows a confirm() dialog before removing the row. On confirm, the row fades to 0.4 opacity with pointer-events: none before DOM removal (visual confirmation the action is in progress). After removal, if no .key-row elements remain, the keys list hides and the .empty empty state appears.
Security considerations for production
The snippet stores the full key in a data-key DOM attribute for demonstration. In a real application, never embed full API keys in the HTML. The recommended pattern: on key creation, return the full key in the API POST response and display it once in a modal with a "Copy and close" button. Store only a hashed (bcrypt or SHA-256) version in the database, along with a short prefix (sk_live_Qb5e) for identification. On subsequent page loads, only fetch the masked prefix from the API — the full key is never retrievable again. This is the same pattern used by Stripe, GitHub, and Vercel.
Key expiry and rotation UI
A production API key manager should support expiry dates and key rotation. Add an "Expires" column to each key row with a date picker for optional expiry. Show a warning badge on keys expiring within 7 days. The "Rotate" action (instead of Revoke) generates a new key, briefly shows both old and new keys active simultaneously for a transition window, then automatically revokes the old key after the window. This zero-downtime rotation pattern prevents service interruption during credential updates.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Instead of manually tracing every string slice, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to walk through exactly how toggleKey() rebuilds the masked string from data-key and why the full key is safe to leave sitting in a data attribute in this demo but not in a real product. The same assistant is useful for optimizing it — ask whether generating the masked prefix once at row-creation time (instead of recomputing it in toggleKey() on every click) would matter at scale, or whether the innerHTML string built in createKey() should be swapped for real DOM node creation to avoid re-parsing markup on every new key. It's also a good extension partner: ask it to add key expiry dates with a warning badge, wire createKey() and revokeKey() to real POST and DELETE endpoints with hashed server-side storage, or add a one-time "copy this key, it won't be shown again" modal on creation. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build an "API key manager" list in plain HTML, CSS, and JavaScript — no framework, no build step.
Requirements:
- Each key is a row storing its full secret value in a data-key attribute on the row element, never rendered as visible text by default.
- Display a masked version built by string slicing: the first 8 characters of the key, followed by a run of bullet characters, followed by the last 4 characters, so the prefix (e.g. sk_live_) stays recognizable while the middle is hidden.
- A show/hide button per row toggles between the masked and full key text, swapping between an eye and an eye-off SVG icon, and tracks its current shown/hidden state in a data attribute on the code element (not a separate JS variable per row).
- A copy button uses navigator.clipboard.writeText on the full key value (regardless of whether it's currently shown or masked), with a fallback to a hidden off-screen textarea plus document.execCommand for browsers without clipboard API support, and swaps its icon to a checkmark with a green tint for 2 seconds after a successful copy.
- A revoke button must call confirm() before doing anything, then fade the row's opacity down and disable its pointer-events, remove the row from the DOM after a short delay, and if no key rows remain, hide the list container and show an empty state panel with a "create your first key" call to action.
- A "New Key" button prompts for a name, generates a random alphanumeric key string, appends a new row built with the same masked-display and action-button structure as the existing rows, and scrolls the new row into view.Step by step
How to Use
- 1Click the eye icon to reveal a keyEach row shows a masked key (sk_live_••••...Qb5e). Click the eye button to toggle between masked and full key display. The icon switches between eye-off and eye-on variants.
- 2Copy a key to clipboardClick the copy icon next to any key. The icon turns green with a checkmark for 2 seconds to confirm. The full key is copied regardless of whether it is masked or shown.
- 3Revoke a keyClick Revoke on any key row. A confirmation dialog appears. On confirm, the row fades and is removed. If all keys are revoked, the empty state appears.
- 4Create a new keyClick the New Key button. Enter a name in the prompt. A new test key row is added at the bottom with a randomly generated key.
- 5Add permission scopesEdit the .perms div in each key row to add read/write/admin chips. Wire these to a modal where users select scopes when creating a key.
- 6Export for your frameworkClick "JSX" for a React component with key state management, masked display, and clipboard integration. Click "Vue" for a Vue 3 SFC.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
For a demo, yes. In production, no — never embed live API keys in the DOM. The standard pattern: on key creation, show the full key once in a modal ("Save this — it won't appear again"), then store only a hashed/masked version in your database. On subsequent page loads, only fetch the masked version. Reveal requests must go to a secure endpoint with auth.
In the browser: crypto.getRandomValues(new Uint8Array(32)) gives 32 cryptographically random bytes. Convert to hex: Array.from(bytes, b => b.toString(16).padStart(2,"0")).join(""). On the server (Node.js): crypto.randomBytes(32).toString("hex") or "base64url". Never use Math.random() for security-sensitive tokens.
Manage keys as const [keys, setKeys] = useState(initialKeys). Each key object: { id, name, masked, full, env, perms, createdAt }. Render a KeyRow component per key with show/hide state in useState(false). revokeKey filters the array: setKeys(keys.filter(k => k.id !== id)). createKey appends a new key object from an API call.
The full key lives in a data-key attribute on the .key-row, never in the visible text. The masked form is built with string slicing — the first 8 characters, a run of bullet characters, then the last 4: fullKey.slice(0, 8) + "•".repeat(n) + fullKey.slice(-4) — which preserves the recognisable sk_live_ prefix while hiding the secret middle. toggleKey() swaps the .key-code element's textContent between the masked and full value, tracks the current state in code.dataset.shown, and switches the eye / eye-off icons to match.
revokeKey() first gates on confirm("Revoke this API key? This cannot be undone."), then plays a soft exit: the row's opacity drops to 0.4 with pointer-events disabled, and after 400ms the row is removed from the DOM. If no .key-row remains, the list is hidden and the #emptyState panel is shown instead, so the UI never ends on a blank list. In production, call your DELETE /keys/:id endpoint before the animation and only remove the row on a successful response — keep the confirm step, because key revocation is irreversible.