<sectionclass="api-keys"aria-labelledby="api-keys-title"data-api-keys><headerclass="api-keys__header"><divclass="api-keys__heading"><h2class="api-keys__title"id="api-keys-title">API keys</h2><pclass="api-keys__text">Keys give full access to your account. Keep them secret and rotate them regularly.</p></div><aclass="btn btn--primary btn--sm"href="#">Create key</a></header><divclass="table-responsive"><tableclass="table api-keys__table"><thead><tr><thscope="col">Name</th><thscope="col">Key</th><thscope="col">Last used</th><thscope="col"><spanclass="sr-only">Actions</span></th></tr></thead><tbody><trdata-api-key="spr_live_51Hx9QeK2vT8mLw3pZr7Yd4N"><thscope="row"><spanclass="api-keys__name">Production</span><spanclass="badge badge--success">Live</span></th><td><divclass="api-keys__key"><codedata-api-key-value>spr_live_••••••••••••••••Yd4N</code><buttonclass="btn btn--ghost-primary btn--icon btn--sm"type="button"aria-pressed="false"hiddendata-api-key-reveal><svgclass="btn__icon"xmlns="http://www.w3.org/2000/svg"viewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="2"stroke-linecap="round"stroke-linejoin="round"aria-hidden="true"focusable="false"><pathd="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7S2 12 2 12z"/><circlecx="12"cy="12"r="3"/></svg><spanclass="sr-only">Show the Production key</span></button><buttonclass="btn btn--ghost-primary btn--icon btn--sm"type="button"hiddendata-api-key-copy><svgclass="btn__icon"xmlns="http://www.w3.org/2000/svg"viewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="2"stroke-linecap="round"stroke-linejoin="round"aria-hidden="true"focusable="false"><rectx="9"y="9"width="13"height="13"rx="2"/><pathd="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg><spanclass="sr-only">Copy the Production key</span></button></div></td><td><timedatetime="2026-10-04T09:30">Today, 09:30</time></td><td><buttonclass="btn btn--outline-danger btn--sm"type="button"data-api-key-revoke>Revoke<spanclass="sr-only"> the Production key</span></button></td></tr><trdata-api-key="spr_test_4eC39HqLyjWDarjtT1zdp7dc"><thscope="row"><spanclass="api-keys__name">Staging</span><spanclass="badge">Test</span></th><td><divclass="api-keys__key"><codedata-api-key-value>spr_test_••••••••••••••••p7dc</code><buttonclass="btn btn--ghost-primary btn--icon btn--sm"type="button"aria-pressed="false"hiddendata-api-key-reveal><svgclass="btn__icon"xmlns="http://www.w3.org/2000/svg"viewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="2"stroke-linecap="round"stroke-linejoin="round"aria-hidden="true"focusable="false"><pathd="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7S2 12 2 12z"/><circlecx="12"cy="12"r="3"/></svg><spanclass="sr-only">Show the Staging key</span></button><buttonclass="btn btn--ghost-primary btn--icon btn--sm"type="button"hiddendata-api-key-copy><svgclass="btn__icon"xmlns="http://www.w3.org/2000/svg"viewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="2"stroke-linecap="round"stroke-linejoin="round"aria-hidden="true"focusable="false"><rectx="9"y="9"width="13"height="13"rx="2"/><pathd="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg><spanclass="sr-only">Copy the Staging key</span></button></div></td><td><timedatetime="2026-09-28T14:02">Sep 28</time></td><td><buttonclass="btn btn--outline-danger btn--sm"type="button"data-api-key-revoke>Revoke<spanclass="sr-only"> the Staging key</span></button></td></tr><tr><thscope="row"><spanclass="api-keys__name">Old CI token</span><spanclass="badge badge--danger">Revoked</span></th><td><code>spr_live_••••••••••••••••Q2aK</code></td><td>Never</td><td></td></tr></tbody></table></div><pclass="sr-only"aria-live="polite"data-api-keys-status></p></section>
(()=>{
document.querySelectorAll('[data-api-keys]').forEach((section)=>{const status = section.querySelector('[data-api-keys-status]');
section.querySelectorAll('[data-api-key]').forEach((row)=>{const key = row.dataset.apiKey;const name = row.querySelector('.api-keys__name').textContent;const value = row.querySelector('[data-api-key-value]');const masked = value.textContent;const reveal = row.querySelector('[data-api-key-reveal]');const copy = row.querySelector('[data-api-key-copy]');// Reveal and copy need JavaScript, so the buttons are hidden without it.
reveal.hidden =false;
copy.hidden =!('clipboard'in navigator);
reveal.addEventListener('click',()=>{const isShown = reveal.getAttribute('aria-pressed')!=='true';
reveal.setAttribute('aria-pressed',String(isShown));
value.textContent = isShown ? key : masked;});
copy.addEventListener('click',async()=>{try{await navigator.clipboard.writeText(key);
status.textContent =`The ${name} key is copied to the clipboard.`;}catch(error){
status.textContent ='Copying failed. Show the key and copy it manually.';}});
row.querySelector('[data-api-key-revoke]').addEventListener('click',(event)=>{// A real app confirms this in a dialog and calls the API.if(!window.confirm(`Revoke the ${name} key? Apps using it will stop working.`))return;
row.setAttribute('data-revoked','');
row.querySelector('.badge').className ='badge badge--danger';
row.querySelector('.badge').textContent ='Revoked';
value.textContent = masked;
reveal.remove();
copy.remove();
event.currentTarget.replaceWith(document.createTextNode(''));
status.textContent =`The ${name} key is revoked.`;
section.querySelector('h2').setAttribute('tabindex','-1');
section.querySelector('h2').focus();});});});})();
API Keys
On this page
A table of API keys with masked values, buttons to show and copy a key, the last use and a revoke action.
Anatomy
.api-keys__header: the title, a short warning and the "Create key" button.
.api-keys__table: a standard Spruce CSS .table in a .table-responsive wrapper; the key name is the row header (th scope="row") with an environment Badge.
.api-keys__key: the masked code and the show/copy icon buttons.
Accessibility and Usability
The show button is a toggle (aria-pressed), and every icon button has hidden text with the key's name ("Copy the Production key"), so the buttons are unique out of context.
Copying and revoking are confirmed in a polite live region.
The key is masked by default, showing only the prefix and the last four characters: enough to recognize it, not enough to use it.
Revoking is destructive, so it asks for confirmation first. After revoking, the row's buttons are removed and the focus moves to the section heading instead of the page.
Technical Details
The full key is in data-api-key here only for the demo. In a real app, never render a secret into the page after it's created: show it once on creation, then only the masked version, and let the server reveal it after a re-authentication.
Copying uses navigator.clipboard.writeText(); the copy button stays hidden where the Clipboard API isn't available (it needs a secure context).
Replace the window.confirm() with a Modal for a consistent look.
Documentation
Learn about Spruce CSS through our extensive documentation.