SolutionsCase StudiesAI ToolsResourcesAboutContact
Get Your Free AI Automation Audit Book a Free 30-Minute Strategy Call
Playground~/frontend Open editor

~/css/copy-buttons

7 CSS Copy Buttons

1 Pure CSS6 with JSPublished 2026-10-05Free · no sign-up

Seven copy-to-clipboard patterns: a copy button on a code block, a masked key with Show and Copy, a clipboard icon that becomes a tick, a tap-to-copy coupon, a Copied! tooltip, a snippet that selects itself in one click and a link copied as plain text, Markdown or HTML.

01. Copy Button on a Code Block

CopyCode

A Copy button in the corner of a code block that copies exactly the code text, then shows Copied for two seconds. The button names what it copies, and the confirmation is announced through a status region.

View code
<div class="cpy1"><pre tabindex="0"><code>npm install @studio/ui</code></pre><button aria-label="Copy install command">Copy</button><span class="sr" role="status"></span></div>
.cpy1 { position: relative; width: 320px; max-width: 100%; font: 14px Inter, system-ui, sans-serif; }
.cpy1 pre { margin: 0; padding: 16px 70px 16px 16px; overflow-x: auto; border-radius: 12px; background: #0b1020; color: #a5f3fc; font: 14px "JetBrains Mono", ui-monospace, monospace; }
.cpy1 pre:focus-visible { outline: 2px solid #67e8f9; outline-offset: 2px; }
.cpy1 button { position: absolute; top: 10px; right: 10px; padding: 5px 10px; border: 1px solid #334155; border-radius: 6px; background: #1e293b; color: #e2e8f0; font: 600 12px Inter, system-ui, sans-serif; cursor: pointer; }
.cpy1 button.ok { border-color: #22c55e; color: #86efac; }
.cpy1 button:focus-visible { outline: 2px solid #67e8f9; outline-offset: 2px; }
.cpy1 .sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
var w = document.querySelector('.cpy1'), b = w.querySelector('button'), st = w.querySelector('.sr');
function fb(t) { var a = document.createElement('textarea'); a.value = t; a.style.position = 'fixed'; a.style.opacity = '0'; document.body.appendChild(a); a.select(); var ok = false; try { ok = document.execCommand('copy'); } catch (e) {} a.remove(); return ok; }
function done(ok) { b.classList.toggle('ok', ok); b.textContent = ok ? 'Copied' : 'Select and copy'; st.textContent = ok ? 'Copied' : ''; setTimeout(function () { b.classList.remove('ok'); b.textContent = 'Copy'; }, 2000); }
b.onclick = function () { var t = w.querySelector('code').textContent; if (navigator.clipboard) navigator.clipboard.writeText(t).then(function () { done(true); }, function () { done(fb(t)); }); else done(fb(t)); };

02. Masked API Key with Copy and Reveal

CopyInput

A read-only field holding a key that is masked by default, with Show and Copy buttons beside it. Copy works whether or not the key is visible, the Show button reports its state with aria-pressed, and the key in the demo is fake.

View code
<div class="cpy2"><label for="cpy2k">API key</label><div class="row"><input id="cpy2k" type="password" readonly value="demo_7f3a21c9e04b58d6"><button class="sh" aria-pressed="false">Show</button><button class="cp">Copy</button></div><p class="st" role="status"></p></div>
.cpy2 { width: 330px; max-width: 100%; font: 14px Inter, system-ui, sans-serif; }
.cpy2 label { display: block; margin-bottom: 4px; color: #101828; font-weight: 700; }
.cpy2 .row { display: flex; border: 1px solid #98a2b3; border-radius: 10px; overflow: hidden; }
.cpy2 input { flex: 1; min-width: 0; padding: 10px; border: 0; background: #f9fafb; color: #101828; font: 13px "JetBrains Mono", ui-monospace, monospace; }
.cpy2 button { padding: 0 12px; border: 0; border-left: 1px solid #d0d5dd; background: #fff; color: #344054; font: 600 13px Inter, system-ui, sans-serif; cursor: pointer; }
.cpy2 .st { min-height: 1.2em; margin: 6px 0 0; color: #067647; font-size: 13px; }
.cpy2 :focus-visible { outline: 3px solid #84adff; outline-offset: -3px; }
var i = document.getElementById('cpy2k'), sh = document.querySelector('.cpy2 .sh'), cp = document.querySelector('.cpy2 .cp'), st = document.querySelector('.cpy2 .st');
sh.onclick = function () { var show = i.type === 'password'; i.type = show ? 'text' : 'password'; sh.textContent = show ? 'Hide' : 'Show'; sh.setAttribute('aria-pressed', show); };
function fb(t) { var a = document.createElement('textarea'); a.value = t; a.style.position = 'fixed'; a.style.opacity = '0'; document.body.appendChild(a); a.select(); var ok = false; try { ok = document.execCommand('copy'); } catch (e) {} a.remove(); return ok; }
cp.onclick = function () { var say = function (ok) { st.textContent = ok ? 'Key copied. Store it somewhere safe.' : 'Copy blocked here. Select the key and press Ctrl+C.'; }; if (navigator.clipboard) navigator.clipboard.writeText(i.value).then(function () { say(true); }, function () { say(fb(i.value)); }); else say(fb(i.value)); };

03. Clipboard Icon Turns into a Tick

CopyIcon

An icon-only copy button whose clipboard icon is swapped for a tick for two seconds after copying. Both icons are in the button and the visible one is chosen by a class, and the button's accessible name changes from Copy to Copied too.

View code
<div class="cpy3"><code>ORDER-7F3A21</code><button aria-label="Copy order number"><svg class="c" viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2"/><path d="M16 8V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h3"/></svg><svg class="k" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12l5 5 9-10"/></svg></button></div>
.cpy3 { display: inline-flex; align-items: center; gap: 6px; padding: 6px 6px 6px 14px; border: 1px solid #334155; border-radius: 10px; background: #111727; }
.cpy3 code { color: #f8fafc; font: 600 14px "JetBrains Mono", ui-monospace, monospace; }
.cpy3 button { display: grid; place-items: center; width: 36px; height: 36px; border: 0; border-radius: 8px; background: #1e293b; cursor: pointer; }
.cpy3 svg { grid-area: 1 / 1; width: 18px; height: 18px; fill: none; stroke: #e2e8f0; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; transition: opacity .2s, transform .2s; }
.cpy3 .k { stroke: #4ade80; opacity: 0; transform: scale(.5); }
.cpy3 button.ok .c { opacity: 0; transform: scale(.5); } .cpy3 button.ok .k { opacity: 1; transform: none; }
.cpy3 button:focus-visible { outline: 2px solid #93c5fd; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .cpy3 svg { transition: none; } }
var b = document.querySelector('.cpy3 button'), t = document.querySelector('.cpy3 code').textContent;
function fb() { var a = document.createElement('textarea'); a.value = t; a.style.position = 'fixed'; a.style.opacity = '0'; document.body.appendChild(a); a.select(); var ok = false; try { ok = document.execCommand('copy'); } catch (e) {} a.remove(); return ok; }
function done(ok) { if (!ok) return; b.classList.add('ok'); b.setAttribute('aria-label', 'Copied'); setTimeout(function () { b.classList.remove('ok'); b.setAttribute('aria-label', 'Copy order number'); }, 2000); }
b.onclick = function () { if (navigator.clipboard) navigator.clipboard.writeText(t).then(function () { done(true); }, function () { done(fb()); }); else done(fb()); };

04. Copyable Coupon Code

CopyEcommerce

A dashed coupon box where the whole code is a button, so a tap anywhere on it copies the code. The button text reads the code and the action (SPRING20, copy code), and a status message confirms the copy.

View code
<div class="cpy4"><p>20% off your first order</p><button><span class="c">SPRING20</span><span class="a">Tap to copy</span></button><p class="st" role="status"></p></div>
.cpy4 { display: grid; justify-items: center; gap: 8px; width: 260px; max-width: 100%; font: 14px Inter, system-ui, sans-serif; }
.cpy4 p { margin: 0; color: #344054; }
.cpy4 button { display: grid; gap: 2px; width: 100%; padding: 12px; border: 2px dashed #f79009; border-radius: 12px; background: #fffaeb; cursor: pointer; }
.cpy4 .c { color: #93370d; font: 800 22px "JetBrains Mono", ui-monospace, monospace; letter-spacing: .1em; } .cpy4 .a { color: #b54708; font-size: 12px; font-weight: 600; }
.cpy4 .st { min-height: 1.2em; color: #067647; font-size: 13px; font-weight: 600; }
.cpy4 button:focus-visible { outline: 3px solid #fdb022; outline-offset: 2px; }
var b = document.querySelector('.cpy4 button'), code = b.querySelector('.c').textContent, a = b.querySelector('.a'), st = document.querySelector('.cpy4 .st');
function fb() { var x = document.createElement('textarea'); x.value = code; x.style.position = 'fixed'; x.style.opacity = '0'; document.body.appendChild(x); x.select(); var ok = false; try { ok = document.execCommand('copy'); } catch (e) {} x.remove(); return ok; }
function done(ok) { a.textContent = ok ? 'Copied!' : 'Copy failed'; st.textContent = ok ? 'Code ' + code + ' copied. Paste it at checkout.' : 'Please write down the code ' + code + '.'; setTimeout(function () { a.textContent = 'Tap to copy'; }, 2000); }
b.onclick = function () { if (navigator.clipboard) navigator.clipboard.writeText(code).then(function () { done(true); }, function () { done(fb()); }); else done(fb()); };

05. Copied Tooltip Above the Button

CopyTooltip

A small Copied! bubble pops up above the button and fades away, a confirmation that does not move anything else on the page. The bubble is aria-hidden decoration; the same message is announced by a status region for screen reader users.

View code
<div class="cpy5"><input aria-label="Share link" readonly value="https://example.com/r/7f3a"><button>Copy<span class="tip" aria-hidden="true">Copied!</span></button><span class="sr" role="status"></span></div>
.cpy5 { display: flex; width: 320px; max-width: 100%; font: 14px Inter, system-ui, sans-serif; }
.cpy5 input { flex: 1; min-width: 0; padding: 10px; border: 1px solid #334155; border-radius: 10px 0 0 10px; background: #0f172a; color: #e2e8f0; font: 13px "JetBrains Mono", ui-monospace, monospace; }
.cpy5 button { position: relative; padding: 0 16px; border: 0; border-radius: 0 10px 10px 0; background: #6366f1; color: #fff; font: 700 14px Inter, system-ui, sans-serif; cursor: pointer; }
.cpy5 .tip { position: absolute; left: 50%; bottom: calc(100% + 8px); padding: 4px 8px; border-radius: 6px; background: #f8fafc; color: #0f172a; font-size: 12px; white-space: nowrap; transform: translate(-50%, 4px); opacity: 0; transition: opacity .2s, transform .2s; pointer-events: none; }
.cpy5 .tip.on { opacity: 1; transform: translate(-50%, 0); }
.cpy5 :focus-visible { outline: 2px solid #a5b4fc; outline-offset: 2px; }
.cpy5 .sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
@media (prefers-reduced-motion: reduce) { .cpy5 .tip { transition: none; } }
var i = document.querySelector('.cpy5 input'), b = document.querySelector('.cpy5 button'), tip = b.querySelector('.tip'), st = document.querySelector('.cpy5 .sr');
function fb() { i.select(); var ok = false; try { ok = document.execCommand('copy'); } catch (e) {} return ok; }
function done(ok) { if (!ok) { st.textContent = 'Copy blocked. Select the link and copy it.'; return; } tip.classList.add('on'); st.textContent = 'Link copied'; setTimeout(function () { tip.classList.remove('on'); }, 1500); }
b.onclick = function () { if (navigator.clipboard) navigator.clipboard.writeText(i.value).then(function () { done(true); }, function () { done(fb()); }); else done(fb()); };

06. Click to Select All

CopySelection

Sometimes the best copy button is no button: user-select: all makes a single click select the whole snippet, ready for Ctrl+C. It works everywhere, needs no permissions, and suits short values like a hash or a URL.

View code
<p class="cpy6">Commit: <code>9f3a21c</code> · Click the code once to select it all.</p>
.cpy6 { margin: 0; color: #475467; font: 14px Inter, system-ui, sans-serif; }
.cpy6 code { padding: 3px 8px; border: 1px solid #d0d5dd; border-radius: 6px; background: #f9fafb; color: #101828; font: 600 14px "JetBrains Mono", ui-monospace, monospace; user-select: all; -webkit-user-select: all; cursor: text; }

07. Copy as Text, Markdown or HTML

CopyFormats

A link can be copied in three formats from a small group of buttons: the plain URL, a Markdown link or an HTML anchor. Each button names its format, and the status line confirms which format went to the clipboard.

View code
<div class="cpy7"><p class="lk">“Calm design notes” · example.com/notes</p><div role="group" aria-label="Copy link as"><button data-f="url">URL</button><button data-f="md">Markdown</button><button data-f="html">HTML</button></div><p class="st" role="status"></p></div>
.cpy7 { width: 320px; max-width: 100%; font: 14px Inter, system-ui, sans-serif; color: #cbd5e1; }
.cpy7 .lk { margin: 0 0 8px; color: #f1f5f9; }
.cpy7 [role=group] { display: flex; gap: 6px; }
.cpy7 button { flex: 1; padding: 8px; border: 1px solid #334155; border-radius: 8px; background: #151b2e; color: #e2e8f0; font: 600 13px Inter, system-ui, sans-serif; cursor: pointer; }
.cpy7 button:hover { background: #1e2640; }
.cpy7 .st { min-height: 1.2em; margin: 8px 0 0; color: #86efac; font-size: 13px; }
.cpy7 button:focus-visible { outline: 2px solid #93c5fd; outline-offset: 2px; }
var st = document.querySelector('.cpy7 .st'), url = 'https://example.com/notes', title = 'Calm design notes';
var fmt = { url: url, md: '[' + title + '](' + url + ')', html: '<a href="' + url + '">' + title + '</a>' };
function fb(t) { var a = document.createElement('textarea'); a.value = t; a.style.position = 'fixed'; a.style.opacity = '0'; document.body.appendChild(a); a.select(); var ok = false; try { ok = document.execCommand('copy'); } catch (e) {} a.remove(); return ok; }
document.querySelectorAll('.cpy7 button').forEach(function (b) { b.onclick = function () { var t = fmt[b.dataset.f], say = function (ok) { st.textContent = ok ? 'Copied as ' + b.textContent : 'Copy blocked in this frame.'; }; if (navigator.clipboard) navigator.clipboard.writeText(t).then(function () { say(true); }, function () { say(fb(t)); }); else say(fb(t)); }; });

About these CSS Copy Buttons

Clipboard API with a fallback

navigator.clipboard.writeText() is the modern way, but it can be blocked in some frames or contexts. Fall back to selecting a hidden textarea and execCommand('copy'), and if that fails too, tell people to press Ctrl+C.

Confirm the copy

Change the button to Copied for a couple of seconds and announce it through a role="status" region, so everyone knows it worked.

Name what is copied

Copy install command or Copy order number is clearer than a row of identical Copy buttons, especially for screen reader users moving between them.

Sometimes no button is needed

user-select: all makes a short value select itself in one click, which works everywhere with no permissions.

Frequently asked questions

Why does copy fail in some previews?

Sandboxed frames can block clipboard access. The fallbacks in each demo handle that case.

Is it safe to copy secrets?

Copying is fine; just avoid logging the value and mask it on screen by default.

Does copy need HTTPS?

The Clipboard API needs a secure context; the fallback works on HTTP too.