Bootstrap Copy-to-Clipboard Feedback — Free HTML CSS JS Snippet
Bootstrap Copy-to-Clipboard Feedback · Buttons · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<div class="container py-5 d-flex justify-content-center">
<div class="card bscopy-card">
<div class="card-body p-4">
<label class="form-label small fw-semibold">API key</label>
<div class="input-group mb-3">
<input type="text" class="form-control" readonly value="sk_live_9f3a2c8e7b1d4f60">
<button type="button" class="btn btn-outline-secondary bscopy-btn" data-copy="sk_live_9f3a2c8e7b1d4f60">Copy</button>
</div>
<label class="form-label small fw-semibold">Referral link</label>
<div class="input-group">
<input type="text" class="form-control" readonly value="https://app.example.com/r/priya-nair">
<button type="button" class="btn btn-outline-secondary bscopy-btn" data-copy="https://app.example.com/r/priya-nair">Copy</button>
</div>
</div>
</div>
</div>.bscopy-card { width: 420px; max-width: 100%; border: 1px solid #eceef1; border-radius: 14px; }
.bscopy-btn { min-width: 88px; transition: background .15s, color .15s, border-color .15s; }
.bscopy-btn.bscopy-copied { background: #198754; border-color: #198754; color: #fff; }const buttons = Array.from(document.querySelectorAll('.bscopy-btn'));
function legacyCopy(text) {
const ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.left = '-9999px';
document.body.appendChild(ta);
ta.focus();
ta.select();
try { document.execCommand('copy'); } catch (e) { /* nothing left to fall back to */ }
document.body.removeChild(ta);
}
function flash(btn) {
if (btn.dataset.busy) return; // ignore rapid re-clicks mid-flash
btn.dataset.busy = '1';
const original = btn.textContent;
btn.textContent = 'Copied!';
btn.classList.add('bscopy-copied');
setTimeout(() => {
btn.textContent = original;
btn.classList.remove('bscopy-copied');
delete btn.dataset.busy;
}, 1600);
}
buttons.forEach(btn => {
btn.addEventListener('click', () => {
const text = btn.dataset.copy;
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(() => flash(btn), () => { legacyCopy(text); flash(btn); });
} else {
legacyCopy(text);
flash(btn);
}
});
});Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Bootstrap Copy-to-Clipboard Feedback — HTML, CSS & JavaScript

A copy button with no feedback leaves a user unsure whether the click actually registered — this snippet's flash() function is the single place that owns the "Copied!" state, swapping the button's label and adding bscopy-copied for 1.6 seconds before reverting both, so the confirmation is impossible to miss without permanently changing the button.
The copy itself tries navigator.clipboard.writeText() first, since it's the modern, promise-based, permission-aware API — but it only works in a secure context (window.isSecureContext), so legacyCopy() provides a real working fallback using a temporary off-screen <textarea> and document.execCommand('copy') for anywhere the modern API isn't available, including .then()'s own rejection path if the user denies clipboard permission.
flash() guards against a click landing while a previous flash is still showing, using a data-busy attribute rather than a separate boolean variable per button — since there are two independent copy buttons on this page sharing one flash() function, storing the busy flag on each button's own dataset (instead of one shared variable) is what keeps copying the API key from resetting the referral link's unrelated flash timer, or vice versa.
Step by step
How to Use
- 1Load the snippetTwo read-only fields show an API key and a referral link, each with its own Copy button.
- 2Click "Copy" next to the API keyThe button turns green and reads "Copied!" for about 1.6 seconds before reverting.
- 3Click it again immediately, mid-flashNothing resets or double-fires — the busy guard ignores the extra click until the current flash finishes.
- 4Click "Copy" on the referral linkIts own button flashes independently, without affecting the API key button's state at all.
- 5Paste the copied text anywhereIt matches exactly what was shown in the field, confirming the clipboard write actually happened.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
It's a deprecated API being phased out of some browsers entirely; navigator.clipboard is the current standard and should always be tried first where available, with execCommand kept purely as a compatibility fallback.
The .then() rejection path on navigator.clipboard.writeText() falls back to legacyCopy(), so the copy still has a real chance of succeeding through the older API instead of silently failing.
This page has two independent copy buttons; a single shared flag would make clicking one button during the other's flash animation either do nothing or incorrectly reset the wrong button's state.
Yes. Keep the copied text and a per-button "flashing" boolean in component state (or a ref, since it doesn't need to trigger extra renders), and drive the temporary label/class change from a timeout the same way flash() does.