09 / 18
All 18 in 18 CSS Settings Pages
- 01Account Settings Page with Section Nav
- 02Billing and Plan Settings Page
- 03Usage and Limits Page with Quota Meters
- 04Invoice History Table with Downloads
- 05Payment Method Settings Card
- 06Team Members Page with Roles
- 07Invite Teammates Form with Role Picker
- 08Roles and Permissions Matrix
- 09API Keys Management Page
- 10Two-Factor Authentication Setup
- 11Active Sessions and Devices Page
- 12Notification Preferences Matrix
- 13Connected Apps and Integrations Page
- 14Appearance and Theme Settings
- 15Data Export Request Page
- 16Audit Log Activity Page
- 17Webhooks Settings Page
- 18Danger Zone Delete Account Section
API Keys Management Page
A developer-grade API keys page: masked secret keys with copy-to-clipboard, scope and environment badges, created / last-used metadata, a safe two-step revoke, and a native <dialog> to create new keys with name, permission level and expiry. The new secret is shown exactly once in a highlighted reveal panel with copy and a clear 'you won't see this again' warning — the pattern used by every serious API platform.
Mosaic by Cruip: an admin template with account, billing and app settings pages · $69
Cruip · Mosaic by Cruip · Affiliate link
The code
<section class="csp-09">
<div class="csp-09__wrap">
<header class="csp-09__head">
<div><h1>API keys</h1><p>Authenticate requests to the Northwind REST API. Keep secret keys out of client-side code.</p></div>
<button type="button" class="csp-09__btn" data-open>Create secret key</button>
</header>
<div class="csp-09__card">
<div class="csp-09__thead" aria-hidden="true"><span>Name</span><span>Key</span><span>Created</span><span>Last used</span><span></span></div>
<ul class="csp-09__list" data-list>
<li class="csp-09__row">
<span class="csp-09__name"><b>Production server</b><span class="csp-09__badges"><em class="is-live">Live</em><em>Full access</em></span></span>
<span class="csp-09__key"><code>sk_live_••••••••••••3f9a</code><button type="button" class="csp-09__icon" data-copy="sk_live_example_3f9a" aria-label="Copy key prefix">Copy</button></span>
<span class="csp-09__meta">Mar 2, 2026</span>
<span class="csp-09__meta"><i class="csp-09__dot"></i>2 minutes ago</span>
<button type="button" class="csp-09__revoke" data-revoke>Revoke</button>
</li>
<li class="csp-09__row">
<span class="csp-09__name"><b>Analytics pipeline</b><span class="csp-09__badges"><em class="is-live">Live</em><em>Read only</em></span></span>
<span class="csp-09__key"><code>sk_live_••••••••••••a71c</code><button type="button" class="csp-09__icon" data-copy="sk_live_example_a71c" aria-label="Copy key prefix">Copy</button></span>
<span class="csp-09__meta">Jun 18, 2026</span>
<span class="csp-09__meta">Yesterday</span>
<button type="button" class="csp-09__revoke" data-revoke>Revoke</button>
</li>
<li class="csp-09__row">
<span class="csp-09__name"><b>Local development</b><span class="csp-09__badges"><em class="is-test">Test</em><em>Restricted</em></span></span>
<span class="csp-09__key"><code>sk_test_••••••••••••09be</code><button type="button" class="csp-09__icon" data-copy="sk_test_example_09be" aria-label="Copy key prefix">Copy</button></span>
<span class="csp-09__meta">Sep 30, 2026</span>
<span class="csp-09__meta csp-09__meta--dim">Never</span>
<button type="button" class="csp-09__revoke" data-revoke>Revoke</button>
</li>
</ul>
</div>
<p class="csp-09__foot">Keys inherit the permissions of their creator. <a href="#">Read the authentication docs →</a></p>
</div>
<dialog class="csp-09__dialog" data-dialog aria-labelledby="csp09-title">
<form class="csp-09__dform" data-form>
<div data-step="create">
<h2 id="csp09-title">Create secret key</h2>
<label class="csp-09__field"><span>Name</span><input name="name" required placeholder="e.g. Billing worker" autocomplete="off"></label>
<fieldset class="csp-09__perms">
<legend>Permissions</legend>
<label><input type="radio" name="perm" value="Full access" checked><span><b>Full access</b><small>Read and write to every resource</small></span></label>
<label><input type="radio" name="perm" value="Restricted"><span><b>Restricted</b><small>Choose specific scopes after creation</small></span></label>
<label><input type="radio" name="perm" value="Read only"><span><b>Read only</b><small>GET requests only</small></span></label>
</fieldset>
<label class="csp-09__field"><span>Expires</span><select name="exp"><option>In 30 days</option><option selected>In 90 days</option><option>In 1 year</option><option>Never</option></select></label>
<div class="csp-09__dfoot"><button type="button" class="csp-09__ghost" data-close>Cancel</button><button type="submit" class="csp-09__btn">Create key</button></div>
</div>
<div data-step="reveal" hidden>
<h2>Save your key</h2>
<p class="csp-09__warn">This is the only time the full key will be shown. Store it in your secrets manager now.</p>
<div class="csp-09__secret"><code data-secret></code><button type="button" class="csp-09__btn" data-copysecret>Copy</button></div>
<div class="csp-09__dfoot"><button type="button" class="csp-09__btn" data-close>Done</button></div>
</div>
</form>
</dialog>
<p class="csp-09__sr" aria-live="polite" data-live></p>
</section><section class="csp-09">
<div class="csp-09__wrap">
<header class="csp-09__head">
<div><h1>API keys</h1><p>Authenticate requests to the Northwind REST API. Keep secret keys out of client-side code.</p></div>
<button type="button" class="csp-09__btn" data-open>Create secret key</button>
</header>
<div class="csp-09__card">
<div class="csp-09__thead" aria-hidden="true"><span>Name</span><span>Key</span><span>Created</span><span>Last used</span><span></span></div>
<ul class="csp-09__list" data-list>
<li class="csp-09__row">
<span class="csp-09__name"><b>Production server</b><span class="csp-09__badges"><em class="is-live">Live</em><em>Full access</em></span></span>
<span class="csp-09__key"><code>sk_live_••••••••••••3f9a</code><button type="button" class="csp-09__icon" data-copy="sk_live_example_3f9a" aria-label="Copy key prefix">Copy</button></span>
<span class="csp-09__meta">Mar 2, 2026</span>
<span class="csp-09__meta"><i class="csp-09__dot"></i>2 minutes ago</span>
<button type="button" class="csp-09__revoke" data-revoke>Revoke</button>
</li>
<li class="csp-09__row">
<span class="csp-09__name"><b>Analytics pipeline</b><span class="csp-09__badges"><em class="is-live">Live</em><em>Read only</em></span></span>
<span class="csp-09__key"><code>sk_live_••••••••••••a71c</code><button type="button" class="csp-09__icon" data-copy="sk_live_example_a71c" aria-label="Copy key prefix">Copy</button></span>
<span class="csp-09__meta">Jun 18, 2026</span>
<span class="csp-09__meta">Yesterday</span>
<button type="button" class="csp-09__revoke" data-revoke>Revoke</button>
</li>
<li class="csp-09__row">
<span class="csp-09__name"><b>Local development</b><span class="csp-09__badges"><em class="is-test">Test</em><em>Restricted</em></span></span>
<span class="csp-09__key"><code>sk_test_••••••••••••09be</code><button type="button" class="csp-09__icon" data-copy="sk_test_example_09be" aria-label="Copy key prefix">Copy</button></span>
<span class="csp-09__meta">Sep 30, 2026</span>
<span class="csp-09__meta csp-09__meta--dim">Never</span>
<button type="button" class="csp-09__revoke" data-revoke>Revoke</button>
</li>
</ul>
</div>
<p class="csp-09__foot">Keys inherit the permissions of their creator. <a href="#">Read the authentication docs →</a></p>
</div>
<dialog class="csp-09__dialog" data-dialog aria-labelledby="csp09-title">
<form class="csp-09__dform" data-form>
<div data-step="create">
<h2 id="csp09-title">Create secret key</h2>
<label class="csp-09__field"><span>Name</span><input name="name" required placeholder="e.g. Billing worker" autocomplete="off"></label>
<fieldset class="csp-09__perms">
<legend>Permissions</legend>
<label><input type="radio" name="perm" value="Full access" checked><span><b>Full access</b><small>Read and write to every resource</small></span></label>
<label><input type="radio" name="perm" value="Restricted"><span><b>Restricted</b><small>Choose specific scopes after creation</small></span></label>
<label><input type="radio" name="perm" value="Read only"><span><b>Read only</b><small>GET requests only</small></span></label>
</fieldset>
<label class="csp-09__field"><span>Expires</span><select name="exp"><option>In 30 days</option><option selected>In 90 days</option><option>In 1 year</option><option>Never</option></select></label>
<div class="csp-09__dfoot"><button type="button" class="csp-09__ghost" data-close>Cancel</button><button type="submit" class="csp-09__btn">Create key</button></div>
</div>
<div data-step="reveal" hidden>
<h2>Save your key</h2>
<p class="csp-09__warn">This is the only time the full key will be shown. Store it in your secrets manager now.</p>
<div class="csp-09__secret"><code data-secret></code><button type="button" class="csp-09__btn" data-copysecret>Copy</button></div>
<div class="csp-09__dfoot"><button type="button" class="csp-09__btn" data-close>Done</button></div>
</div>
</form>
</dialog>
<p class="csp-09__sr" aria-live="polite" data-live></p>
</section>.csp-09,
.csp-09 *,
.csp-09 *::before,
.csp-09 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.csp-09 {
--bg: oklch(0.14 0.008 150);
--surface: oklch(0.18 0.01 150);
--raised: oklch(0.22 0.012 150);
--text: oklch(0.95 0.008 150);
--muted: oklch(0.68 0.015 150);
--line: oklch(0.28 0.012 150);
--accent: oklch(0.86 0.19 128);
--accent-ink: oklch(0.22 0.06 128);
--bad: oklch(0.68 0.19 25);
color-scheme: dark;
container-type: inline-size;
min-height: 100vh;
min-height: 100dvh;
padding: 40px 22px;
font-family: system-ui,'Segoe UI',sans-serif;
background: var(--bg);
color: var(--text);
accent-color: var(--accent);
width: 100%;
}
.csp-09__sr {
position: absolute!important;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
.csp-09__wrap {
max-width: 980px;
margin-inline: auto;
display: flex;
flex-direction: column;
gap: 18px;
}
.csp-09__head {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
}
.csp-09__head h1 {
font: 700 30px/1.1 system-ui,sans-serif;
letter-spacing: -.025em;
}
.csp-09__head p {
max-width: 520px;
font: 400 14.5px/1.5 system-ui,sans-serif;
color: var(--muted);
margin-top: 6px;
text-wrap: pretty;
}
.csp-09__btn {
min-height: 44px;
padding: 0 18px;
border: 0;
border-radius: 10px;
font: 650 14px/1 system-ui,sans-serif;
color: var(--accent-ink);
background: var(--accent);
cursor: pointer;
transition: filter .15s,transform .1s;
}
.csp-09__btn:hover {
filter: brightness(1.07);
}
.csp-09__btn:active {
transform: scale(.98);
}
.csp-09__ghost {
min-height: 44px;
padding: 0 18px;
border: 1px solid var(--line);
border-radius: 10px;
font: 600 14px/1 system-ui,sans-serif;
color: var(--text);
background: transparent;
cursor: pointer;
}
.csp-09__btn:focus-visible,
.csp-09__ghost:focus-visible,
.csp-09__icon:focus-visible,
.csp-09__revoke:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.csp-09__card {
border: 1px solid var(--line);
border-radius: 16px;
background: var(--surface);
overflow: hidden;
}
.csp-09__thead,
.csp-09__row {
display: grid;
grid-template-columns: minmax(0,1.3fr) minmax(0,1.6fr) 110px 130px 96px;
align-items: center;
gap: 14px;
padding: 0 18px;
}
.csp-09__thead {
min-height: 40px;
font: 600 11.5px/1 ui-monospace,Menlo,monospace;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--muted);
border-bottom: 1px solid var(--line);
background: oklch(1 0 0/.02);
}
.csp-09__list {
list-style: none;
}
.csp-09__row {
min-height: 72px;
padding-block: 12px;
border-bottom: 1px solid var(--line);
transition: background .15s,opacity .3s;
}
.csp-09__row:last-child {
border-bottom: 0;
}
.csp-09__row:hover {
background: oklch(1 0 0/.025);
}
.csp-09__row.is-new {
animation: csp09-flash 1.8s ease;
}
.csp-09__row.is-revoked {
opacity: .45;
}
.csp-09__row.is-revoked code {
text-decoration: line-through;
}
.csp-09__name {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 0;
}
.csp-09__name b {
font: 600 14px/1.2 system-ui,sans-serif;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.csp-09__badges {
display: flex;
gap: 5px;
flex-wrap: wrap;
}
.csp-09__badges em {
font: 600 10.5px/1 system-ui,sans-serif;
font-style: normal;
padding: 4px 7px;
border-radius: 5px;
color: var(--muted);
background: oklch(1 0 0/.06);
}
.csp-09__badges em.is-live {
color: var(--accent);
background: oklch(0.86 0.19 128/.12);
}
.csp-09__badges em.is-test {
color: oklch(0.82 0.13 75);
background: oklch(0.82 0.13 75/.12);
}
.csp-09__badges em.is-revoked {
color: var(--bad);
background: oklch(0.68 0.19 25/.14);
}
.csp-09__key {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.csp-09__key code {
flex: 1;
min-width: 0;
padding: 7px 10px;
border-radius: 8px;
font: 500 12.5px/1 ui-monospace,Menlo,monospace;
font-variant-ligatures: none;
color: var(--text);
background: var(--bg);
border: 1px solid var(--line);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.csp-09__icon {
flex: none;
min-height: 32px;
padding: 0 10px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--raised);
font: 600 12px/1 system-ui,sans-serif;
color: var(--text);
cursor: pointer;
transition: all .15s;
}
.csp-09__icon:hover {
border-color: oklch(0.4 0.02 150);
}
.csp-09__icon.is-done {
color: var(--accent-ink);
background: var(--accent);
border-color: transparent;
}
.csp-09__meta {
display: flex;
align-items: center;
gap: 7px;
font: 400 13px/1.2 system-ui,sans-serif;
color: var(--muted);
}
.csp-09__meta--dim {
opacity: .6;
}
.csp-09__dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 0 3px oklch(0.86 0.19 128/.2);
}
.csp-09__revoke {
justify-self: end;
min-height: 36px;
padding: 0 12px;
border: 1px solid transparent;
border-radius: 8px;
background: transparent;
font: 600 13px/1 system-ui,sans-serif;
color: var(--muted);
cursor: pointer;
transition: all .15s;
white-space: nowrap;
}
.csp-09__revoke:hover {
color: var(--bad);
border-color: oklch(0.68 0.19 25/.4);
}
.csp-09__revoke.is-confirm {
color: #fff;
background: oklch(0.55 0.2 25);
border-color: transparent;
}
.csp-09__revoke:disabled {
visibility: hidden;
}
.csp-09__foot {
font: 400 13px/1.5 system-ui,sans-serif;
color: var(--muted);
}
.csp-09__foot a {
color: var(--accent);
text-decoration: none;
font-weight: 600;
}
.csp-09__dialog {
margin: auto;
width: min(460px,calc(100% - 32px));
padding: 0;
border: 1px solid var(--line);
border-radius: 20px;
background: var(--surface);
color: var(--text);
box-shadow: 0 40px 80px -30px oklch(0 0 0/.8);
opacity: 1;
translate: 0 0;
transition: opacity .25s,translate .25s,overlay .25s allow-discrete,display .25s allow-discrete;
}
.csp-09__dialog::backdrop {
background: oklch(0.08 0.01 150/.7);
backdrop-filter: blur(4px);
}
@starting-style {
.csp-09__dialog[open] {
opacity: 0;
translate: 0 14px;
}
}
.csp-09__dform {
padding: 26px;
}
.csp-09__dform>div {
display: flex;
flex-direction: column;
gap: 16px;
}
.csp-09__dform h2 {
font: 700 20px/1.2 system-ui,sans-serif;
letter-spacing: -.015em;
}
.csp-09__field {
display: flex;
flex-direction: column;
gap: 7px;
font: 550 13px/1 system-ui,sans-serif;
color: var(--muted);
}
.csp-09__field input,
.csp-09__field select {
min-height: 44px;
padding: 0 12px;
border: 1px solid var(--line);
border-radius: 10px;
background: var(--bg);
font: 400 14.5px/1 system-ui,sans-serif;
color: var(--text);
}
.csp-09__field input:focus,
.csp-09__field select:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 4px oklch(0.86 0.19 128/.15);
}
.csp-09__perms {
border: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.csp-09__perms legend {
font: 550 13px/1 system-ui,sans-serif;
color: var(--muted);
margin-bottom: 8px;
}
.csp-09__perms label {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border: 1px solid var(--line);
border-radius: 11px;
cursor: pointer;
transition: border-color .15s,background .15s;
}
.csp-09__perms label:has(input:checked) {
border-color: var(--accent);
background: oklch(0.86 0.19 128/.06);
}
.csp-09__perms input {
width: 17px;
height: 17px;
}
.csp-09__perms span {
display: flex;
flex-direction: column;
gap: 3px;
}
.csp-09__perms b {
font: 600 13.5px/1.2 system-ui,sans-serif;
}
.csp-09__perms small {
font: 400 12.5px/1.3 system-ui,sans-serif;
color: var(--muted);
}
.csp-09__dfoot {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 4px;
}
.csp-09__warn {
padding: 12px 14px;
border-radius: 11px;
font: 400 13.5px/1.5 system-ui,sans-serif;
color: oklch(0.88 0.1 80);
background: oklch(0.82 0.13 75/.1);
border: 1px solid oklch(0.82 0.13 75/.25);
}
.csp-09__secret {
display: flex;
gap: 8px;
align-items: stretch;
}
.csp-09__secret code {
flex: 1;
min-width: 0;
padding: 12px;
border-radius: 10px;
font: 500 13px/1.4 ui-monospace,Menlo,monospace;
font-variant-ligatures: none;
word-break: break-all;
color: var(--accent);
background: var(--bg);
border: 1px dashed oklch(0.86 0.19 128/.4);
}
@keyframes csp09-flash {
0% {
background: oklch(0.86 0.19 128/.16);
}
100% {
background: transparent;
}
}
@container (max-width: 760px) {
.csp-09__thead {
display: none;
}
.csp-09__row {
grid-template-columns: minmax(0,1fr) auto;
grid-template-areas: "name revoke" "key key" "c u";
}
.csp-09__name {
grid-area: name;
}
.csp-09__key {
grid-area: key;
}
.csp-09__revoke {
grid-area: revoke;
}
.csp-09__row>.csp-09__meta:nth-of-type(3) {
grid-area: c;
}
.csp-09__row>.csp-09__meta:nth-of-type(4) {
grid-area: u;
justify-self: end;
}
}
@media (prefers-reduced-motion: reduce) {
.csp-09 * {
transition: none!important;
animation: none!important;
}
}.csp-09,
.csp-09 *,
.csp-09 *::before,
.csp-09 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.csp-09 {
--bg: oklch(0.14 0.008 150);
--surface: oklch(0.18 0.01 150);
--raised: oklch(0.22 0.012 150);
--text: oklch(0.95 0.008 150);
--muted: oklch(0.68 0.015 150);
--line: oklch(0.28 0.012 150);
--accent: oklch(0.86 0.19 128);
--accent-ink: oklch(0.22 0.06 128);
--bad: oklch(0.68 0.19 25);
color-scheme: dark;
container-type: inline-size;
min-height: 100vh;
min-height: 100dvh;
padding: 40px 22px;
font-family: system-ui,'Segoe UI',sans-serif;
background: var(--bg);
color: var(--text);
accent-color: var(--accent);
width: 100%;
}
.csp-09__sr {
position: absolute!important;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
.csp-09__wrap {
max-width: 980px;
margin-inline: auto;
display: flex;
flex-direction: column;
gap: 18px;
}
.csp-09__head {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
}
.csp-09__head h1 {
font: 700 30px/1.1 system-ui,sans-serif;
letter-spacing: -.025em;
}
.csp-09__head p {
max-width: 520px;
font: 400 14.5px/1.5 system-ui,sans-serif;
color: var(--muted);
margin-top: 6px;
text-wrap: pretty;
}
.csp-09__btn {
min-height: 44px;
padding: 0 18px;
border: 0;
border-radius: 10px;
font: 650 14px/1 system-ui,sans-serif;
color: var(--accent-ink);
background: var(--accent);
cursor: pointer;
transition: filter .15s,transform .1s;
}
.csp-09__btn:hover {
filter: brightness(1.07);
}
.csp-09__btn:active {
transform: scale(.98);
}
.csp-09__ghost {
min-height: 44px;
padding: 0 18px;
border: 1px solid var(--line);
border-radius: 10px;
font: 600 14px/1 system-ui,sans-serif;
color: var(--text);
background: transparent;
cursor: pointer;
}
.csp-09__btn:focus-visible,
.csp-09__ghost:focus-visible,
.csp-09__icon:focus-visible,
.csp-09__revoke:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.csp-09__card {
border: 1px solid var(--line);
border-radius: 16px;
background: var(--surface);
overflow: hidden;
}
.csp-09__thead,
.csp-09__row {
display: grid;
grid-template-columns: minmax(0,1.3fr) minmax(0,1.6fr) 110px 130px 96px;
align-items: center;
gap: 14px;
padding: 0 18px;
}
.csp-09__thead {
min-height: 40px;
font: 600 11.5px/1 ui-monospace,Menlo,monospace;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--muted);
border-bottom: 1px solid var(--line);
background: oklch(1 0 0/.02);
}
.csp-09__list {
list-style: none;
}
.csp-09__row {
min-height: 72px;
padding-block: 12px;
border-bottom: 1px solid var(--line);
transition: background .15s,opacity .3s;
}
.csp-09__row:last-child {
border-bottom: 0;
}
.csp-09__row:hover {
background: oklch(1 0 0/.025);
}
.csp-09__row.is-new {
animation: csp09-flash 1.8s ease;
}
.csp-09__row.is-revoked {
opacity: .45;
}
.csp-09__row.is-revoked code {
text-decoration: line-through;
}
.csp-09__name {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 0;
}
.csp-09__name b {
font: 600 14px/1.2 system-ui,sans-serif;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.csp-09__badges {
display: flex;
gap: 5px;
flex-wrap: wrap;
}
.csp-09__badges em {
font: 600 10.5px/1 system-ui,sans-serif;
font-style: normal;
padding: 4px 7px;
border-radius: 5px;
color: var(--muted);
background: oklch(1 0 0/.06);
}
.csp-09__badges em.is-live {
color: var(--accent);
background: oklch(0.86 0.19 128/.12);
}
.csp-09__badges em.is-test {
color: oklch(0.82 0.13 75);
background: oklch(0.82 0.13 75/.12);
}
.csp-09__badges em.is-revoked {
color: var(--bad);
background: oklch(0.68 0.19 25/.14);
}
.csp-09__key {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.csp-09__key code {
flex: 1;
min-width: 0;
padding: 7px 10px;
border-radius: 8px;
font: 500 12.5px/1 ui-monospace,Menlo,monospace;
font-variant-ligatures: none;
color: var(--text);
background: var(--bg);
border: 1px solid var(--line);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.csp-09__icon {
flex: none;
min-height: 32px;
padding: 0 10px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--raised);
font: 600 12px/1 system-ui,sans-serif;
color: var(--text);
cursor: pointer;
transition: all .15s;
}
.csp-09__icon:hover {
border-color: oklch(0.4 0.02 150);
}
.csp-09__icon.is-done {
color: var(--accent-ink);
background: var(--accent);
border-color: transparent;
}
.csp-09__meta {
display: flex;
align-items: center;
gap: 7px;
font: 400 13px/1.2 system-ui,sans-serif;
color: var(--muted);
}
.csp-09__meta--dim {
opacity: .6;
}
.csp-09__dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 0 3px oklch(0.86 0.19 128/.2);
}
.csp-09__revoke {
justify-self: end;
min-height: 36px;
padding: 0 12px;
border: 1px solid transparent;
border-radius: 8px;
background: transparent;
font: 600 13px/1 system-ui,sans-serif;
color: var(--muted);
cursor: pointer;
transition: all .15s;
white-space: nowrap;
}
.csp-09__revoke:hover {
color: var(--bad);
border-color: oklch(0.68 0.19 25/.4);
}
.csp-09__revoke.is-confirm {
color: #fff;
background: oklch(0.55 0.2 25);
border-color: transparent;
}
.csp-09__revoke:disabled {
visibility: hidden;
}
.csp-09__foot {
font: 400 13px/1.5 system-ui,sans-serif;
color: var(--muted);
}
.csp-09__foot a {
color: var(--accent);
text-decoration: none;
font-weight: 600;
}
.csp-09__dialog {
margin: auto;
width: min(460px,calc(100% - 32px));
padding: 0;
border: 1px solid var(--line);
border-radius: 20px;
background: var(--surface);
color: var(--text);
box-shadow: 0 40px 80px -30px oklch(0 0 0/.8);
opacity: 1;
translate: 0 0;
transition: opacity .25s,translate .25s,overlay .25s allow-discrete,display .25s allow-discrete;
}
.csp-09__dialog::backdrop {
background: oklch(0.08 0.01 150/.7);
backdrop-filter: blur(4px);
}
@starting-style {
.csp-09__dialog[open] {
opacity: 0;
translate: 0 14px;
}
}
.csp-09__dform {
padding: 26px;
}
.csp-09__dform>div {
display: flex;
flex-direction: column;
gap: 16px;
}
.csp-09__dform h2 {
font: 700 20px/1.2 system-ui,sans-serif;
letter-spacing: -.015em;
}
.csp-09__field {
display: flex;
flex-direction: column;
gap: 7px;
font: 550 13px/1 system-ui,sans-serif;
color: var(--muted);
}
.csp-09__field input,
.csp-09__field select {
min-height: 44px;
padding: 0 12px;
border: 1px solid var(--line);
border-radius: 10px;
background: var(--bg);
font: 400 14.5px/1 system-ui,sans-serif;
color: var(--text);
}
.csp-09__field input:focus,
.csp-09__field select:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 4px oklch(0.86 0.19 128/.15);
}
.csp-09__perms {
border: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.csp-09__perms legend {
font: 550 13px/1 system-ui,sans-serif;
color: var(--muted);
margin-bottom: 8px;
}
.csp-09__perms label {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border: 1px solid var(--line);
border-radius: 11px;
cursor: pointer;
transition: border-color .15s,background .15s;
}
.csp-09__perms label:has(input:checked) {
border-color: var(--accent);
background: oklch(0.86 0.19 128/.06);
}
.csp-09__perms input {
width: 17px;
height: 17px;
}
.csp-09__perms span {
display: flex;
flex-direction: column;
gap: 3px;
}
.csp-09__perms b {
font: 600 13.5px/1.2 system-ui,sans-serif;
}
.csp-09__perms small {
font: 400 12.5px/1.3 system-ui,sans-serif;
color: var(--muted);
}
.csp-09__dfoot {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 4px;
}
.csp-09__warn {
padding: 12px 14px;
border-radius: 11px;
font: 400 13.5px/1.5 system-ui,sans-serif;
color: oklch(0.88 0.1 80);
background: oklch(0.82 0.13 75/.1);
border: 1px solid oklch(0.82 0.13 75/.25);
}
.csp-09__secret {
display: flex;
gap: 8px;
align-items: stretch;
}
.csp-09__secret code {
flex: 1;
min-width: 0;
padding: 12px;
border-radius: 10px;
font: 500 13px/1.4 ui-monospace,Menlo,monospace;
font-variant-ligatures: none;
word-break: break-all;
color: var(--accent);
background: var(--bg);
border: 1px dashed oklch(0.86 0.19 128/.4);
}
@keyframes csp09-flash {
0% {
background: oklch(0.86 0.19 128/.16);
}
100% {
background: transparent;
}
}
@container (max-width: 760px) {
.csp-09__thead {
display: none;
}
.csp-09__row {
grid-template-columns: minmax(0,1fr) auto;
grid-template-areas: "name revoke" "key key" "c u";
}
.csp-09__name {
grid-area: name;
}
.csp-09__key {
grid-area: key;
}
.csp-09__revoke {
grid-area: revoke;
}
.csp-09__row>.csp-09__meta:nth-of-type(3) {
grid-area: c;
}
.csp-09__row>.csp-09__meta:nth-of-type(4) {
grid-area: u;
justify-self: end;
}
}
@media (prefers-reduced-motion: reduce) {
.csp-09 * {
transition: none!important;
animation: none!important;
}
}(function () {
var root = document.querySelector('.csp-09');
var dlg = root.querySelector('[data-dialog]'), form = root.querySelector('[data-form]');
var list = root.querySelector('[data-list]'), live = root.querySelector('[data-live]');
var stepCreate = form.querySelector('[data-step=create]'), stepReveal = form.querySelector('[data-step=reveal]');
function copy(text, btn) {
var done = function () {
var old = btn.textContent; btn.classList.add('is-done'); btn.textContent = 'Copied'; live.textContent = 'Copied to clipboard';
setTimeout(function () { btn.classList.remove('is-done'); btn.textContent = old; }, 1600);
};
if (navigator.clipboard) navigator.clipboard.writeText(text).then(done, done); else done();
}
function genKey() {
var a = new Uint8Array(24), chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', s = '';
crypto.getRandomValues(a); a.forEach(function (n) { s += chars[n % chars.length]; });
return 'sk_live_' + s;
}
root.querySelector('[data-open]').addEventListener('click', function () {
form.reset(); stepCreate.hidden = false; stepReveal.hidden = true; dlg.showModal();
});
root.querySelectorAll('[data-close]').forEach(function (b) { b.addEventListener('click', function () { dlg.close(); }); });
form.addEventListener('submit', function (e) {
e.preventDefault();
if (!form.reportValidity()) return;
var name = form.elements.name.value.trim(), perm = form.elements.perm.value, key = genKey();
var li = document.createElement('li');
li.className = 'csp-09__row is-new';
li.innerHTML = '<span class="csp-09__name"><b></b><span class="csp-09__badges"><em class="is-live">Live</em><em></em></span></span>' +
'<span class="csp-09__key"><code></code><button type="button" class="csp-09__icon" aria-label="Copy key prefix">Copy</button></span>' +
'<span class="csp-09__meta">Just now</span><span class="csp-09__meta csp-09__meta--dim">Never</span>' +
'<button type="button" class="csp-09__revoke" data-revoke>Revoke</button>';
li.querySelector('b').textContent = name;
li.querySelectorAll('em')[1].textContent = perm;
li.querySelector('code').textContent = 'sk_live_••••••••••••' + key.slice(-4);
li.querySelector('.csp-09__icon').dataset.copy = key.slice(0, 12) + '…' + key.slice(-4);
list.prepend(li);
form.querySelector('[data-secret]').textContent = key;
stepCreate.hidden = true; stepReveal.hidden = false;
});
form.querySelector('[data-copysecret]').addEventListener('click', function (e) { copy(form.querySelector('[data-secret]').textContent, e.currentTarget); });
root.addEventListener('click', function (e) {
var c = e.target.closest('[data-copy]'); if (c) copy(c.dataset.copy, c);
var r = e.target.closest('[data-revoke]'); if (!r) return;
if (!r.classList.contains('is-confirm')) {
r.classList.add('is-confirm'); r.textContent = 'Confirm';
clearTimeout(r._t); r._t = setTimeout(function () { r.classList.remove('is-confirm'); r.textContent = 'Revoke'; }, 4000);
return;
}
var row = r.closest('.csp-09__row');
row.classList.add('is-revoked');
var badge = row.querySelector('.csp-09__badges em'); badge.className = 'is-revoked'; badge.textContent = 'Revoked';
r.disabled = true; live.textContent = 'Key revoked';
});
})();(function () {
var root = document.querySelector('.csp-09');
var dlg = root.querySelector('[data-dialog]'), form = root.querySelector('[data-form]');
var list = root.querySelector('[data-list]'), live = root.querySelector('[data-live]');
var stepCreate = form.querySelector('[data-step=create]'), stepReveal = form.querySelector('[data-step=reveal]');
function copy(text, btn) {
var done = function () {
var old = btn.textContent; btn.classList.add('is-done'); btn.textContent = 'Copied'; live.textContent = 'Copied to clipboard';
setTimeout(function () { btn.classList.remove('is-done'); btn.textContent = old; }, 1600);
};
if (navigator.clipboard) navigator.clipboard.writeText(text).then(done, done); else done();
}
function genKey() {
var a = new Uint8Array(24), chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', s = '';
crypto.getRandomValues(a); a.forEach(function (n) { s += chars[n % chars.length]; });
return 'sk_live_' + s;
}
root.querySelector('[data-open]').addEventListener('click', function () {
form.reset(); stepCreate.hidden = false; stepReveal.hidden = true; dlg.showModal();
});
root.querySelectorAll('[data-close]').forEach(function (b) { b.addEventListener('click', function () { dlg.close(); }); });
form.addEventListener('submit', function (e) {
e.preventDefault();
if (!form.reportValidity()) return;
var name = form.elements.name.value.trim(), perm = form.elements.perm.value, key = genKey();
var li = document.createElement('li');
li.className = 'csp-09__row is-new';
li.innerHTML = '<span class="csp-09__name"><b></b><span class="csp-09__badges"><em class="is-live">Live</em><em></em></span></span>' +
'<span class="csp-09__key"><code></code><button type="button" class="csp-09__icon" aria-label="Copy key prefix">Copy</button></span>' +
'<span class="csp-09__meta">Just now</span><span class="csp-09__meta csp-09__meta--dim">Never</span>' +
'<button type="button" class="csp-09__revoke" data-revoke>Revoke</button>';
li.querySelector('b').textContent = name;
li.querySelectorAll('em')[1].textContent = perm;
li.querySelector('code').textContent = 'sk_live_••••••••••••' + key.slice(-4);
li.querySelector('.csp-09__icon').dataset.copy = key.slice(0, 12) + '…' + key.slice(-4);
list.prepend(li);
form.querySelector('[data-secret]').textContent = key;
stepCreate.hidden = true; stepReveal.hidden = false;
});
form.querySelector('[data-copysecret]').addEventListener('click', function (e) { copy(form.querySelector('[data-secret]').textContent, e.currentTarget); });
root.addEventListener('click', function (e) {
var c = e.target.closest('[data-copy]'); if (c) copy(c.dataset.copy, c);
var r = e.target.closest('[data-revoke]'); if (!r) return;
if (!r.classList.contains('is-confirm')) {
r.classList.add('is-confirm'); r.textContent = 'Confirm';
clearTimeout(r._t); r._t = setTimeout(function () { r.classList.remove('is-confirm'); r.textContent = 'Revoke'; }, 4000);
return;
}
var row = r.closest('.csp-09__row');
row.classList.add('is-revoked');
var badge = row.querySelector('.csp-09__badges em'); badge.className = 'is-revoked'; badge.textContent = 'Revoked';
r.disabled = true; live.textContent = 'Key revoked';
});
})();Here's a working CSS Settings Page from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: API Keys Management Page
Source: https://codefronts.com/layouts/css-settings-pages/api-keys-management/
A developer-grade API keys page: masked secret keys with copy-to-clipboard, scope and environment badges, created / last-used metadata, a safe two-step revoke, and a native <dialog> to create new keys with name, permission level and expiry. The new secret is shown exactly once in a highlighted reveal panel with copy and a clear 'you won't see this again' warning — the pattern used by every serious API platform.
## HTML
```html
<section class="csp-09">
<div class="csp-09__wrap">
<header class="csp-09__head">
<div><h1>API keys</h1><p>Authenticate requests to the Northwind REST API. Keep secret keys out of client-side code.</p></div>
<button type="button" class="csp-09__btn" data-open>Create secret key</button>
</header>
<div class="csp-09__card">
<div class="csp-09__thead" aria-hidden="true"><span>Name</span><span>Key</span><span>Created</span><span>Last used</span><span></span></div>
<ul class="csp-09__list" data-list>
<li class="csp-09__row">
<span class="csp-09__name"><b>Production server</b><span class="csp-09__badges"><em class="is-live">Live</em><em>Full access</em></span></span>
<span class="csp-09__key"><code>sk_live_••••••••••••3f9a</code><button type="button" class="csp-09__icon" data-copy="sk_live_example_3f9a" aria-label="Copy key prefix">Copy</button></span>
<span class="csp-09__meta">Mar 2, 2026</span>
<span class="csp-09__meta"><i class="csp-09__dot"></i>2 minutes ago</span>
<button type="button" class="csp-09__revoke" data-revoke>Revoke</button>
</li>
<li class="csp-09__row">
<span class="csp-09__name"><b>Analytics pipeline</b><span class="csp-09__badges"><em class="is-live">Live</em><em>Read only</em></span></span>
<span class="csp-09__key"><code>sk_live_••••••••••••a71c</code><button type="button" class="csp-09__icon" data-copy="sk_live_example_a71c" aria-label="Copy key prefix">Copy</button></span>
<span class="csp-09__meta">Jun 18, 2026</span>
<span class="csp-09__meta">Yesterday</span>
<button type="button" class="csp-09__revoke" data-revoke>Revoke</button>
</li>
<li class="csp-09__row">
<span class="csp-09__name"><b>Local development</b><span class="csp-09__badges"><em class="is-test">Test</em><em>Restricted</em></span></span>
<span class="csp-09__key"><code>sk_test_••••••••••••09be</code><button type="button" class="csp-09__icon" data-copy="sk_test_example_09be" aria-label="Copy key prefix">Copy</button></span>
<span class="csp-09__meta">Sep 30, 2026</span>
<span class="csp-09__meta csp-09__meta--dim">Never</span>
<button type="button" class="csp-09__revoke" data-revoke>Revoke</button>
</li>
</ul>
</div>
<p class="csp-09__foot">Keys inherit the permissions of their creator. <a href="#">Read the authentication docs →</a></p>
</div>
<dialog class="csp-09__dialog" data-dialog aria-labelledby="csp09-title">
<form class="csp-09__dform" data-form>
<div data-step="create">
<h2 id="csp09-title">Create secret key</h2>
<label class="csp-09__field"><span>Name</span><input name="name" required placeholder="e.g. Billing worker" autocomplete="off"></label>
<fieldset class="csp-09__perms">
<legend>Permissions</legend>
<label><input type="radio" name="perm" value="Full access" checked><span><b>Full access</b><small>Read and write to every resource</small></span></label>
<label><input type="radio" name="perm" value="Restricted"><span><b>Restricted</b><small>Choose specific scopes after creation</small></span></label>
<label><input type="radio" name="perm" value="Read only"><span><b>Read only</b><small>GET requests only</small></span></label>
</fieldset>
<label class="csp-09__field"><span>Expires</span><select name="exp"><option>In 30 days</option><option selected>In 90 days</option><option>In 1 year</option><option>Never</option></select></label>
<div class="csp-09__dfoot"><button type="button" class="csp-09__ghost" data-close>Cancel</button><button type="submit" class="csp-09__btn">Create key</button></div>
</div>
<div data-step="reveal" hidden>
<h2>Save your key</h2>
<p class="csp-09__warn">This is the only time the full key will be shown. Store it in your secrets manager now.</p>
<div class="csp-09__secret"><code data-secret></code><button type="button" class="csp-09__btn" data-copysecret>Copy</button></div>
<div class="csp-09__dfoot"><button type="button" class="csp-09__btn" data-close>Done</button></div>
</div>
</form>
</dialog>
<p class="csp-09__sr" aria-live="polite" data-live></p>
</section>
```
## CSS
```css
.csp-09,
.csp-09 *,
.csp-09 *::before,
.csp-09 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.csp-09 {
--bg: oklch(0.14 0.008 150);
--surface: oklch(0.18 0.01 150);
--raised: oklch(0.22 0.012 150);
--text: oklch(0.95 0.008 150);
--muted: oklch(0.68 0.015 150);
--line: oklch(0.28 0.012 150);
--accent: oklch(0.86 0.19 128);
--accent-ink: oklch(0.22 0.06 128);
--bad: oklch(0.68 0.19 25);
color-scheme: dark;
container-type: inline-size;
min-height: 100vh;
min-height: 100dvh;
padding: 40px 22px;
font-family: system-ui,'Segoe UI',sans-serif;
background: var(--bg);
color: var(--text);
accent-color: var(--accent);
width: 100%;
}
.csp-09__sr {
position: absolute!important;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
.csp-09__wrap {
max-width: 980px;
margin-inline: auto;
display: flex;
flex-direction: column;
gap: 18px;
}
.csp-09__head {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
}
.csp-09__head h1 {
font: 700 30px/1.1 system-ui,sans-serif;
letter-spacing: -.025em;
}
.csp-09__head p {
max-width: 520px;
font: 400 14.5px/1.5 system-ui,sans-serif;
color: var(--muted);
margin-top: 6px;
text-wrap: pretty;
}
.csp-09__btn {
min-height: 44px;
padding: 0 18px;
border: 0;
border-radius: 10px;
font: 650 14px/1 system-ui,sans-serif;
color: var(--accent-ink);
background: var(--accent);
cursor: pointer;
transition: filter .15s,transform .1s;
}
.csp-09__btn:hover {
filter: brightness(1.07);
}
.csp-09__btn:active {
transform: scale(.98);
}
.csp-09__ghost {
min-height: 44px;
padding: 0 18px;
border: 1px solid var(--line);
border-radius: 10px;
font: 600 14px/1 system-ui,sans-serif;
color: var(--text);
background: transparent;
cursor: pointer;
}
.csp-09__btn:focus-visible,
.csp-09__ghost:focus-visible,
.csp-09__icon:focus-visible,
.csp-09__revoke:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.csp-09__card {
border: 1px solid var(--line);
border-radius: 16px;
background: var(--surface);
overflow: hidden;
}
.csp-09__thead,
.csp-09__row {
display: grid;
grid-template-columns: minmax(0,1.3fr) minmax(0,1.6fr) 110px 130px 96px;
align-items: center;
gap: 14px;
padding: 0 18px;
}
.csp-09__thead {
min-height: 40px;
font: 600 11.5px/1 ui-monospace,Menlo,monospace;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--muted);
border-bottom: 1px solid var(--line);
background: oklch(1 0 0/.02);
}
.csp-09__list {
list-style: none;
}
.csp-09__row {
min-height: 72px;
padding-block: 12px;
border-bottom: 1px solid var(--line);
transition: background .15s,opacity .3s;
}
.csp-09__row:last-child {
border-bottom: 0;
}
.csp-09__row:hover {
background: oklch(1 0 0/.025);
}
.csp-09__row.is-new {
animation: csp09-flash 1.8s ease;
}
.csp-09__row.is-revoked {
opacity: .45;
}
.csp-09__row.is-revoked code {
text-decoration: line-through;
}
.csp-09__name {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 0;
}
.csp-09__name b {
font: 600 14px/1.2 system-ui,sans-serif;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.csp-09__badges {
display: flex;
gap: 5px;
flex-wrap: wrap;
}
.csp-09__badges em {
font: 600 10.5px/1 system-ui,sans-serif;
font-style: normal;
padding: 4px 7px;
border-radius: 5px;
color: var(--muted);
background: oklch(1 0 0/.06);
}
.csp-09__badges em.is-live {
color: var(--accent);
background: oklch(0.86 0.19 128/.12);
}
.csp-09__badges em.is-test {
color: oklch(0.82 0.13 75);
background: oklch(0.82 0.13 75/.12);
}
.csp-09__badges em.is-revoked {
color: var(--bad);
background: oklch(0.68 0.19 25/.14);
}
.csp-09__key {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.csp-09__key code {
flex: 1;
min-width: 0;
padding: 7px 10px;
border-radius: 8px;
font: 500 12.5px/1 ui-monospace,Menlo,monospace;
font-variant-ligatures: none;
color: var(--text);
background: var(--bg);
border: 1px solid var(--line);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.csp-09__icon {
flex: none;
min-height: 32px;
padding: 0 10px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--raised);
font: 600 12px/1 system-ui,sans-serif;
color: var(--text);
cursor: pointer;
transition: all .15s;
}
.csp-09__icon:hover {
border-color: oklch(0.4 0.02 150);
}
.csp-09__icon.is-done {
color: var(--accent-ink);
background: var(--accent);
border-color: transparent;
}
.csp-09__meta {
display: flex;
align-items: center;
gap: 7px;
font: 400 13px/1.2 system-ui,sans-serif;
color: var(--muted);
}
.csp-09__meta--dim {
opacity: .6;
}
.csp-09__dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 0 3px oklch(0.86 0.19 128/.2);
}
.csp-09__revoke {
justify-self: end;
min-height: 36px;
padding: 0 12px;
border: 1px solid transparent;
border-radius: 8px;
background: transparent;
font: 600 13px/1 system-ui,sans-serif;
color: var(--muted);
cursor: pointer;
transition: all .15s;
white-space: nowrap;
}
.csp-09__revoke:hover {
color: var(--bad);
border-color: oklch(0.68 0.19 25/.4);
}
.csp-09__revoke.is-confirm {
color: #fff;
background: oklch(0.55 0.2 25);
border-color: transparent;
}
.csp-09__revoke:disabled {
visibility: hidden;
}
.csp-09__foot {
font: 400 13px/1.5 system-ui,sans-serif;
color: var(--muted);
}
.csp-09__foot a {
color: var(--accent);
text-decoration: none;
font-weight: 600;
}
.csp-09__dialog {
margin: auto;
width: min(460px,calc(100% - 32px));
padding: 0;
border: 1px solid var(--line);
border-radius: 20px;
background: var(--surface);
color: var(--text);
box-shadow: 0 40px 80px -30px oklch(0 0 0/.8);
opacity: 1;
translate: 0 0;
transition: opacity .25s,translate .25s,overlay .25s allow-discrete,display .25s allow-discrete;
}
.csp-09__dialog::backdrop {
background: oklch(0.08 0.01 150/.7);
backdrop-filter: blur(4px);
}
@starting-style {
.csp-09__dialog[open] {
opacity: 0;
translate: 0 14px;
}
}
.csp-09__dform {
padding: 26px;
}
.csp-09__dform>div {
display: flex;
flex-direction: column;
gap: 16px;
}
.csp-09__dform h2 {
font: 700 20px/1.2 system-ui,sans-serif;
letter-spacing: -.015em;
}
.csp-09__field {
display: flex;
flex-direction: column;
gap: 7px;
font: 550 13px/1 system-ui,sans-serif;
color: var(--muted);
}
.csp-09__field input,
.csp-09__field select {
min-height: 44px;
padding: 0 12px;
border: 1px solid var(--line);
border-radius: 10px;
background: var(--bg);
font: 400 14.5px/1 system-ui,sans-serif;
color: var(--text);
}
.csp-09__field input:focus,
.csp-09__field select:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 4px oklch(0.86 0.19 128/.15);
}
.csp-09__perms {
border: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.csp-09__perms legend {
font: 550 13px/1 system-ui,sans-serif;
color: var(--muted);
margin-bottom: 8px;
}
.csp-09__perms label {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border: 1px solid var(--line);
border-radius: 11px;
cursor: pointer;
transition: border-color .15s,background .15s;
}
.csp-09__perms label:has(input:checked) {
border-color: var(--accent);
background: oklch(0.86 0.19 128/.06);
}
.csp-09__perms input {
width: 17px;
height: 17px;
}
.csp-09__perms span {
display: flex;
flex-direction: column;
gap: 3px;
}
.csp-09__perms b {
font: 600 13.5px/1.2 system-ui,sans-serif;
}
.csp-09__perms small {
font: 400 12.5px/1.3 system-ui,sans-serif;
color: var(--muted);
}
.csp-09__dfoot {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 4px;
}
.csp-09__warn {
padding: 12px 14px;
border-radius: 11px;
font: 400 13.5px/1.5 system-ui,sans-serif;
color: oklch(0.88 0.1 80);
background: oklch(0.82 0.13 75/.1);
border: 1px solid oklch(0.82 0.13 75/.25);
}
.csp-09__secret {
display: flex;
gap: 8px;
align-items: stretch;
}
.csp-09__secret code {
flex: 1;
min-width: 0;
padding: 12px;
border-radius: 10px;
font: 500 13px/1.4 ui-monospace,Menlo,monospace;
font-variant-ligatures: none;
word-break: break-all;
color: var(--accent);
background: var(--bg);
border: 1px dashed oklch(0.86 0.19 128/.4);
}
@keyframes csp09-flash {
0% {
background: oklch(0.86 0.19 128/.16);
}
100% {
background: transparent;
}
}
@container (max-width: 760px) {
.csp-09__thead {
display: none;
}
.csp-09__row {
grid-template-columns: minmax(0,1fr) auto;
grid-template-areas: "name revoke" "key key" "c u";
}
.csp-09__name {
grid-area: name;
}
.csp-09__key {
grid-area: key;
}
.csp-09__revoke {
grid-area: revoke;
}
.csp-09__row>.csp-09__meta:nth-of-type(3) {
grid-area: c;
}
.csp-09__row>.csp-09__meta:nth-of-type(4) {
grid-area: u;
justify-self: end;
}
}
@media (prefers-reduced-motion: reduce) {
.csp-09 * {
transition: none!important;
animation: none!important;
}
}
```
## JavaScript
```js
(function () {
var root = document.querySelector('.csp-09');
var dlg = root.querySelector('[data-dialog]'), form = root.querySelector('[data-form]');
var list = root.querySelector('[data-list]'), live = root.querySelector('[data-live]');
var stepCreate = form.querySelector('[data-step=create]'), stepReveal = form.querySelector('[data-step=reveal]');
function copy(text, btn) {
var done = function () {
var old = btn.textContent; btn.classList.add('is-done'); btn.textContent = 'Copied'; live.textContent = 'Copied to clipboard';
setTimeout(function () { btn.classList.remove('is-done'); btn.textContent = old; }, 1600);
};
if (navigator.clipboard) navigator.clipboard.writeText(text).then(done, done); else done();
}
function genKey() {
var a = new Uint8Array(24), chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', s = '';
crypto.getRandomValues(a); a.forEach(function (n) { s += chars[n % chars.length]; });
return 'sk_live_' + s;
}
root.querySelector('[data-open]').addEventListener('click', function () {
form.reset(); stepCreate.hidden = false; stepReveal.hidden = true; dlg.showModal();
});
root.querySelectorAll('[data-close]').forEach(function (b) { b.addEventListener('click', function () { dlg.close(); }); });
form.addEventListener('submit', function (e) {
e.preventDefault();
if (!form.reportValidity()) return;
var name = form.elements.name.value.trim(), perm = form.elements.perm.value, key = genKey();
var li = document.createElement('li');
li.className = 'csp-09__row is-new';
li.innerHTML = '<span class="csp-09__name"><b></b><span class="csp-09__badges"><em class="is-live">Live</em><em></em></span></span>' +
'<span class="csp-09__key"><code></code><button type="button" class="csp-09__icon" aria-label="Copy key prefix">Copy</button></span>' +
'<span class="csp-09__meta">Just now</span><span class="csp-09__meta csp-09__meta--dim">Never</span>' +
'<button type="button" class="csp-09__revoke" data-revoke>Revoke</button>';
li.querySelector('b').textContent = name;
li.querySelectorAll('em')[1].textContent = perm;
li.querySelector('code').textContent = 'sk_live_••••••••••••' + key.slice(-4);
li.querySelector('.csp-09__icon').dataset.copy = key.slice(0, 12) + '…' + key.slice(-4);
list.prepend(li);
form.querySelector('[data-secret]').textContent = key;
stepCreate.hidden = true; stepReveal.hidden = false;
});
form.querySelector('[data-copysecret]').addEventListener('click', function (e) { copy(form.querySelector('[data-secret]').textContent, e.currentTarget); });
root.addEventListener('click', function (e) {
var c = e.target.closest('[data-copy]'); if (c) copy(c.dataset.copy, c);
var r = e.target.closest('[data-revoke]'); if (!r) return;
if (!r.classList.contains('is-confirm')) {
r.classList.add('is-confirm'); r.textContent = 'Confirm';
clearTimeout(r._t); r._t = setTimeout(function () { r.classList.remove('is-confirm'); r.textContent = 'Revoke'; }, 4000);
return;
}
var row = r.closest('.csp-09__row');
row.classList.add('is-revoked');
var badge = row.querySelector('.csp-09__badges em'); badge.className = 'is-revoked'; badge.textContent = 'Revoked';
r.disabled = true; live.textContent = 'Key revoked';
});
})();
```Here's a working CSS Settings Page from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: API Keys Management Page
Source: https://codefronts.com/layouts/css-settings-pages/api-keys-management/
A developer-grade API keys page: masked secret keys with copy-to-clipboard, scope and environment badges, created / last-used metadata, a safe two-step revoke, and a native <dialog> to create new keys with name, permission level and expiry. The new secret is shown exactly once in a highlighted reveal panel with copy and a clear 'you won't see this again' warning — the pattern used by every serious API platform.
## HTML
```html
<section class="csp-09">
<div class="csp-09__wrap">
<header class="csp-09__head">
<div><h1>API keys</h1><p>Authenticate requests to the Northwind REST API. Keep secret keys out of client-side code.</p></div>
<button type="button" class="csp-09__btn" data-open>Create secret key</button>
</header>
<div class="csp-09__card">
<div class="csp-09__thead" aria-hidden="true"><span>Name</span><span>Key</span><span>Created</span><span>Last used</span><span></span></div>
<ul class="csp-09__list" data-list>
<li class="csp-09__row">
<span class="csp-09__name"><b>Production server</b><span class="csp-09__badges"><em class="is-live">Live</em><em>Full access</em></span></span>
<span class="csp-09__key"><code>sk_live_••••••••••••3f9a</code><button type="button" class="csp-09__icon" data-copy="sk_live_example_3f9a" aria-label="Copy key prefix">Copy</button></span>
<span class="csp-09__meta">Mar 2, 2026</span>
<span class="csp-09__meta"><i class="csp-09__dot"></i>2 minutes ago</span>
<button type="button" class="csp-09__revoke" data-revoke>Revoke</button>
</li>
<li class="csp-09__row">
<span class="csp-09__name"><b>Analytics pipeline</b><span class="csp-09__badges"><em class="is-live">Live</em><em>Read only</em></span></span>
<span class="csp-09__key"><code>sk_live_••••••••••••a71c</code><button type="button" class="csp-09__icon" data-copy="sk_live_example_a71c" aria-label="Copy key prefix">Copy</button></span>
<span class="csp-09__meta">Jun 18, 2026</span>
<span class="csp-09__meta">Yesterday</span>
<button type="button" class="csp-09__revoke" data-revoke>Revoke</button>
</li>
<li class="csp-09__row">
<span class="csp-09__name"><b>Local development</b><span class="csp-09__badges"><em class="is-test">Test</em><em>Restricted</em></span></span>
<span class="csp-09__key"><code>sk_test_••••••••••••09be</code><button type="button" class="csp-09__icon" data-copy="sk_test_example_09be" aria-label="Copy key prefix">Copy</button></span>
<span class="csp-09__meta">Sep 30, 2026</span>
<span class="csp-09__meta csp-09__meta--dim">Never</span>
<button type="button" class="csp-09__revoke" data-revoke>Revoke</button>
</li>
</ul>
</div>
<p class="csp-09__foot">Keys inherit the permissions of their creator. <a href="#">Read the authentication docs →</a></p>
</div>
<dialog class="csp-09__dialog" data-dialog aria-labelledby="csp09-title">
<form class="csp-09__dform" data-form>
<div data-step="create">
<h2 id="csp09-title">Create secret key</h2>
<label class="csp-09__field"><span>Name</span><input name="name" required placeholder="e.g. Billing worker" autocomplete="off"></label>
<fieldset class="csp-09__perms">
<legend>Permissions</legend>
<label><input type="radio" name="perm" value="Full access" checked><span><b>Full access</b><small>Read and write to every resource</small></span></label>
<label><input type="radio" name="perm" value="Restricted"><span><b>Restricted</b><small>Choose specific scopes after creation</small></span></label>
<label><input type="radio" name="perm" value="Read only"><span><b>Read only</b><small>GET requests only</small></span></label>
</fieldset>
<label class="csp-09__field"><span>Expires</span><select name="exp"><option>In 30 days</option><option selected>In 90 days</option><option>In 1 year</option><option>Never</option></select></label>
<div class="csp-09__dfoot"><button type="button" class="csp-09__ghost" data-close>Cancel</button><button type="submit" class="csp-09__btn">Create key</button></div>
</div>
<div data-step="reveal" hidden>
<h2>Save your key</h2>
<p class="csp-09__warn">This is the only time the full key will be shown. Store it in your secrets manager now.</p>
<div class="csp-09__secret"><code data-secret></code><button type="button" class="csp-09__btn" data-copysecret>Copy</button></div>
<div class="csp-09__dfoot"><button type="button" class="csp-09__btn" data-close>Done</button></div>
</div>
</form>
</dialog>
<p class="csp-09__sr" aria-live="polite" data-live></p>
</section>
```
## CSS
```css
.csp-09,
.csp-09 *,
.csp-09 *::before,
.csp-09 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.csp-09 {
--bg: oklch(0.14 0.008 150);
--surface: oklch(0.18 0.01 150);
--raised: oklch(0.22 0.012 150);
--text: oklch(0.95 0.008 150);
--muted: oklch(0.68 0.015 150);
--line: oklch(0.28 0.012 150);
--accent: oklch(0.86 0.19 128);
--accent-ink: oklch(0.22 0.06 128);
--bad: oklch(0.68 0.19 25);
color-scheme: dark;
container-type: inline-size;
min-height: 100vh;
min-height: 100dvh;
padding: 40px 22px;
font-family: system-ui,'Segoe UI',sans-serif;
background: var(--bg);
color: var(--text);
accent-color: var(--accent);
width: 100%;
}
.csp-09__sr {
position: absolute!important;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
.csp-09__wrap {
max-width: 980px;
margin-inline: auto;
display: flex;
flex-direction: column;
gap: 18px;
}
.csp-09__head {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
}
.csp-09__head h1 {
font: 700 30px/1.1 system-ui,sans-serif;
letter-spacing: -.025em;
}
.csp-09__head p {
max-width: 520px;
font: 400 14.5px/1.5 system-ui,sans-serif;
color: var(--muted);
margin-top: 6px;
text-wrap: pretty;
}
.csp-09__btn {
min-height: 44px;
padding: 0 18px;
border: 0;
border-radius: 10px;
font: 650 14px/1 system-ui,sans-serif;
color: var(--accent-ink);
background: var(--accent);
cursor: pointer;
transition: filter .15s,transform .1s;
}
.csp-09__btn:hover {
filter: brightness(1.07);
}
.csp-09__btn:active {
transform: scale(.98);
}
.csp-09__ghost {
min-height: 44px;
padding: 0 18px;
border: 1px solid var(--line);
border-radius: 10px;
font: 600 14px/1 system-ui,sans-serif;
color: var(--text);
background: transparent;
cursor: pointer;
}
.csp-09__btn:focus-visible,
.csp-09__ghost:focus-visible,
.csp-09__icon:focus-visible,
.csp-09__revoke:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.csp-09__card {
border: 1px solid var(--line);
border-radius: 16px;
background: var(--surface);
overflow: hidden;
}
.csp-09__thead,
.csp-09__row {
display: grid;
grid-template-columns: minmax(0,1.3fr) minmax(0,1.6fr) 110px 130px 96px;
align-items: center;
gap: 14px;
padding: 0 18px;
}
.csp-09__thead {
min-height: 40px;
font: 600 11.5px/1 ui-monospace,Menlo,monospace;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--muted);
border-bottom: 1px solid var(--line);
background: oklch(1 0 0/.02);
}
.csp-09__list {
list-style: none;
}
.csp-09__row {
min-height: 72px;
padding-block: 12px;
border-bottom: 1px solid var(--line);
transition: background .15s,opacity .3s;
}
.csp-09__row:last-child {
border-bottom: 0;
}
.csp-09__row:hover {
background: oklch(1 0 0/.025);
}
.csp-09__row.is-new {
animation: csp09-flash 1.8s ease;
}
.csp-09__row.is-revoked {
opacity: .45;
}
.csp-09__row.is-revoked code {
text-decoration: line-through;
}
.csp-09__name {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 0;
}
.csp-09__name b {
font: 600 14px/1.2 system-ui,sans-serif;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.csp-09__badges {
display: flex;
gap: 5px;
flex-wrap: wrap;
}
.csp-09__badges em {
font: 600 10.5px/1 system-ui,sans-serif;
font-style: normal;
padding: 4px 7px;
border-radius: 5px;
color: var(--muted);
background: oklch(1 0 0/.06);
}
.csp-09__badges em.is-live {
color: var(--accent);
background: oklch(0.86 0.19 128/.12);
}
.csp-09__badges em.is-test {
color: oklch(0.82 0.13 75);
background: oklch(0.82 0.13 75/.12);
}
.csp-09__badges em.is-revoked {
color: var(--bad);
background: oklch(0.68 0.19 25/.14);
}
.csp-09__key {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.csp-09__key code {
flex: 1;
min-width: 0;
padding: 7px 10px;
border-radius: 8px;
font: 500 12.5px/1 ui-monospace,Menlo,monospace;
font-variant-ligatures: none;
color: var(--text);
background: var(--bg);
border: 1px solid var(--line);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.csp-09__icon {
flex: none;
min-height: 32px;
padding: 0 10px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--raised);
font: 600 12px/1 system-ui,sans-serif;
color: var(--text);
cursor: pointer;
transition: all .15s;
}
.csp-09__icon:hover {
border-color: oklch(0.4 0.02 150);
}
.csp-09__icon.is-done {
color: var(--accent-ink);
background: var(--accent);
border-color: transparent;
}
.csp-09__meta {
display: flex;
align-items: center;
gap: 7px;
font: 400 13px/1.2 system-ui,sans-serif;
color: var(--muted);
}
.csp-09__meta--dim {
opacity: .6;
}
.csp-09__dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 0 3px oklch(0.86 0.19 128/.2);
}
.csp-09__revoke {
justify-self: end;
min-height: 36px;
padding: 0 12px;
border: 1px solid transparent;
border-radius: 8px;
background: transparent;
font: 600 13px/1 system-ui,sans-serif;
color: var(--muted);
cursor: pointer;
transition: all .15s;
white-space: nowrap;
}
.csp-09__revoke:hover {
color: var(--bad);
border-color: oklch(0.68 0.19 25/.4);
}
.csp-09__revoke.is-confirm {
color: #fff;
background: oklch(0.55 0.2 25);
border-color: transparent;
}
.csp-09__revoke:disabled {
visibility: hidden;
}
.csp-09__foot {
font: 400 13px/1.5 system-ui,sans-serif;
color: var(--muted);
}
.csp-09__foot a {
color: var(--accent);
text-decoration: none;
font-weight: 600;
}
.csp-09__dialog {
margin: auto;
width: min(460px,calc(100% - 32px));
padding: 0;
border: 1px solid var(--line);
border-radius: 20px;
background: var(--surface);
color: var(--text);
box-shadow: 0 40px 80px -30px oklch(0 0 0/.8);
opacity: 1;
translate: 0 0;
transition: opacity .25s,translate .25s,overlay .25s allow-discrete,display .25s allow-discrete;
}
.csp-09__dialog::backdrop {
background: oklch(0.08 0.01 150/.7);
backdrop-filter: blur(4px);
}
@starting-style {
.csp-09__dialog[open] {
opacity: 0;
translate: 0 14px;
}
}
.csp-09__dform {
padding: 26px;
}
.csp-09__dform>div {
display: flex;
flex-direction: column;
gap: 16px;
}
.csp-09__dform h2 {
font: 700 20px/1.2 system-ui,sans-serif;
letter-spacing: -.015em;
}
.csp-09__field {
display: flex;
flex-direction: column;
gap: 7px;
font: 550 13px/1 system-ui,sans-serif;
color: var(--muted);
}
.csp-09__field input,
.csp-09__field select {
min-height: 44px;
padding: 0 12px;
border: 1px solid var(--line);
border-radius: 10px;
background: var(--bg);
font: 400 14.5px/1 system-ui,sans-serif;
color: var(--text);
}
.csp-09__field input:focus,
.csp-09__field select:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 4px oklch(0.86 0.19 128/.15);
}
.csp-09__perms {
border: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.csp-09__perms legend {
font: 550 13px/1 system-ui,sans-serif;
color: var(--muted);
margin-bottom: 8px;
}
.csp-09__perms label {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border: 1px solid var(--line);
border-radius: 11px;
cursor: pointer;
transition: border-color .15s,background .15s;
}
.csp-09__perms label:has(input:checked) {
border-color: var(--accent);
background: oklch(0.86 0.19 128/.06);
}
.csp-09__perms input {
width: 17px;
height: 17px;
}
.csp-09__perms span {
display: flex;
flex-direction: column;
gap: 3px;
}
.csp-09__perms b {
font: 600 13.5px/1.2 system-ui,sans-serif;
}
.csp-09__perms small {
font: 400 12.5px/1.3 system-ui,sans-serif;
color: var(--muted);
}
.csp-09__dfoot {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 4px;
}
.csp-09__warn {
padding: 12px 14px;
border-radius: 11px;
font: 400 13.5px/1.5 system-ui,sans-serif;
color: oklch(0.88 0.1 80);
background: oklch(0.82 0.13 75/.1);
border: 1px solid oklch(0.82 0.13 75/.25);
}
.csp-09__secret {
display: flex;
gap: 8px;
align-items: stretch;
}
.csp-09__secret code {
flex: 1;
min-width: 0;
padding: 12px;
border-radius: 10px;
font: 500 13px/1.4 ui-monospace,Menlo,monospace;
font-variant-ligatures: none;
word-break: break-all;
color: var(--accent);
background: var(--bg);
border: 1px dashed oklch(0.86 0.19 128/.4);
}
@keyframes csp09-flash {
0% {
background: oklch(0.86 0.19 128/.16);
}
100% {
background: transparent;
}
}
@container (max-width: 760px) {
.csp-09__thead {
display: none;
}
.csp-09__row {
grid-template-columns: minmax(0,1fr) auto;
grid-template-areas: "name revoke" "key key" "c u";
}
.csp-09__name {
grid-area: name;
}
.csp-09__key {
grid-area: key;
}
.csp-09__revoke {
grid-area: revoke;
}
.csp-09__row>.csp-09__meta:nth-of-type(3) {
grid-area: c;
}
.csp-09__row>.csp-09__meta:nth-of-type(4) {
grid-area: u;
justify-self: end;
}
}
@media (prefers-reduced-motion: reduce) {
.csp-09 * {
transition: none!important;
animation: none!important;
}
}
```
## JavaScript
```js
(function () {
var root = document.querySelector('.csp-09');
var dlg = root.querySelector('[data-dialog]'), form = root.querySelector('[data-form]');
var list = root.querySelector('[data-list]'), live = root.querySelector('[data-live]');
var stepCreate = form.querySelector('[data-step=create]'), stepReveal = form.querySelector('[data-step=reveal]');
function copy(text, btn) {
var done = function () {
var old = btn.textContent; btn.classList.add('is-done'); btn.textContent = 'Copied'; live.textContent = 'Copied to clipboard';
setTimeout(function () { btn.classList.remove('is-done'); btn.textContent = old; }, 1600);
};
if (navigator.clipboard) navigator.clipboard.writeText(text).then(done, done); else done();
}
function genKey() {
var a = new Uint8Array(24), chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', s = '';
crypto.getRandomValues(a); a.forEach(function (n) { s += chars[n % chars.length]; });
return 'sk_live_' + s;
}
root.querySelector('[data-open]').addEventListener('click', function () {
form.reset(); stepCreate.hidden = false; stepReveal.hidden = true; dlg.showModal();
});
root.querySelectorAll('[data-close]').forEach(function (b) { b.addEventListener('click', function () { dlg.close(); }); });
form.addEventListener('submit', function (e) {
e.preventDefault();
if (!form.reportValidity()) return;
var name = form.elements.name.value.trim(), perm = form.elements.perm.value, key = genKey();
var li = document.createElement('li');
li.className = 'csp-09__row is-new';
li.innerHTML = '<span class="csp-09__name"><b></b><span class="csp-09__badges"><em class="is-live">Live</em><em></em></span></span>' +
'<span class="csp-09__key"><code></code><button type="button" class="csp-09__icon" aria-label="Copy key prefix">Copy</button></span>' +
'<span class="csp-09__meta">Just now</span><span class="csp-09__meta csp-09__meta--dim">Never</span>' +
'<button type="button" class="csp-09__revoke" data-revoke>Revoke</button>';
li.querySelector('b').textContent = name;
li.querySelectorAll('em')[1].textContent = perm;
li.querySelector('code').textContent = 'sk_live_••••••••••••' + key.slice(-4);
li.querySelector('.csp-09__icon').dataset.copy = key.slice(0, 12) + '…' + key.slice(-4);
list.prepend(li);
form.querySelector('[data-secret]').textContent = key;
stepCreate.hidden = true; stepReveal.hidden = false;
});
form.querySelector('[data-copysecret]').addEventListener('click', function (e) { copy(form.querySelector('[data-secret]').textContent, e.currentTarget); });
root.addEventListener('click', function (e) {
var c = e.target.closest('[data-copy]'); if (c) copy(c.dataset.copy, c);
var r = e.target.closest('[data-revoke]'); if (!r) return;
if (!r.classList.contains('is-confirm')) {
r.classList.add('is-confirm'); r.textContent = 'Confirm';
clearTimeout(r._t); r._t = setTimeout(function () { r.classList.remove('is-confirm'); r.textContent = 'Revoke'; }, 4000);
return;
}
var row = r.closest('.csp-09__row');
row.classList.add('is-revoked');
var badge = row.querySelector('.csp-09__badges em'); badge.className = 'is-revoked'; badge.textContent = 'Revoked';
r.disabled = true; live.textContent = 'Key revoked';
});
})();
```How this works
The create flow uses the native <dialog> element opened with showModal(), which gives you a focus trap, Escape-to-close, an inert background and a stylable ::backdrop for free. The form inside uses method=dialog semantics, and on submit the script generates a random key with crypto.getRandomValues, prepends a new row, and swaps the dialog body to a one-time reveal panel.
Existing keys are never shown in full — only prefix and last four characters, set in a monospace font with font-variant-ligatures:none so characters like 0/O stay distinct. Copy buttons use the Clipboard API and flip to a 'Copied' state. Revoke is two-step: the first click turns the button into 'Confirm revoke' for four seconds; the second strikes the row through and marks it revoked, preventing one-click disasters without a modal.
Techniques used: native <dialog> with showModal() + ::backdrop · crypto.getRandomValues key generation · one-time secret reveal panel · masked keys with prefix + last 4 · Clipboard API copy state · two-step inline revoke confirm · @starting-style dialog entry animation
dialog[open] { opacity: 1; translate: 0 0; }
@starting-style { dialog[open] { opacity: 0; translate: 0 12px; } }
dialog::backdrop { background: oklch(0 0 0 / .6); backdrop-filter: blur(4px); }Make it yours
- Add per-key IP allowlists as a textarea in the dialog and show a shield badge on restricted keys.
- Show a usage sparkline per key using the bar pattern from Demo 17.
- Split keys into Live and Test tabs with a radio group and the :has() filter pattern from Demo 04.
- Add 'Roll key' that creates a replacement and schedules the old one to expire in 24h.
Gotchas — read before shipping
- Never return full secrets from your API after creation — store only a hash. The UI should make that limitation obvious.
- Treat 'last used' as approximate and cache it; don't hit your logs on every page view.
- Clipboard writes can fail in insecure contexts or sandboxed iframes — always show feedback either way.
@starting-styleis Chrome 117+/Safari 17.5+/Firefox 129+; older browsers just open instantly.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 117+ | 17.5+ | 129+ | 117+ |
<dialog> + showModal() is universal (2022+). @starting-style only affects the open animation.