Reusable Copy-to-Clipboard Button with Vanilla JavaScript
Run it. Change it. Make it yours.
One-file front-end editor
Write plain HTML, CSS, JavaScript, or CDN-powered Vue and React demos.
Use case
Copy-to-clipboard components are useful when users need to quickly copy values such as email addresses, coupon codes, referral codes, share links, account IDs, API endpoints, or other short pieces of text.
This version combines the JavaScript functionality with a polished responsive interface so the result looks like a real reusable UI component instead of plain browser elements.
What this snippet does
- Displays copyable values inside clean card-style rows.
- Uses responsive styling that works on desktop and mobile screens.
- Copies text using the modern
navigator.clipboard.writeText()API. - Provides a legacy
document.execCommand("copy")fallback. - Supports paragraphs, code elements, inputs, and textareas.
- Shows temporary success feedback after copying.
- Includes accessible status feedback with
aria-live. - Uses one delegated click listener for multiple copy buttons.
Complete snippet
Paste this complete example into an HTML file or a browser playground. No npm package or external library is required.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Copy to Clipboard Component</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 24px;
font-family: Inter, Arial, sans-serif;
background: linear-gradient(135deg, #f8fafc, #eef2ff);
color: #0f172a;
}
.copy-card {
width: min(100%, 620px);
padding: 28px;
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 20px;
box-shadow: 0 20px 50px rgba(15, 23, 42, 0.10);
}
.copy-card__badge {
display: inline-flex;
align-items: center;
padding: 6px 10px;
margin-bottom: 12px;
border-radius: 999px;
background: #ecfdf5;
color: #047857;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.copy-card h1 {
margin: 0;
font-size: clamp(1.5rem, 4vw, 2rem);
line-height: 1.2;
}
.copy-card__intro {
margin: 10px 0 24px;
color: #64748b;
line-height: 1.6;
}
.copy-list {
display: grid;
gap: 14px;
}
.copy-item {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: 14px;
padding: 16px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 14px;
transition: border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
}
.copy-item:hover {
border-color: #cbd5e1;
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.06);
}
.copy-item__content {
min-width: 0;
}
.copy-item__label {
display: block;
margin-bottom: 6px;
color: #64748b;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.copy-item__value {
display: block;
margin: 0;
overflow-wrap: anywhere;
color: #0f172a;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 0.95rem;
font-weight: 600;
}
.copy-button {
min-width: 92px;
border: 0;
border-radius: 10px;
padding: 10px 16px;
background: #0f172a;
color: #ffffff;
font: inherit;
font-weight: 700;
cursor: pointer;
transition: transform 0.15s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.copy-button:hover {
background: #1e293b;
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.18);
}
.copy-button:active {
transform: scale(0.97);
}
.copy-button:focus-visible {
outline: 3px solid #a5b4fc;
outline-offset: 3px;
}
.copy-button.is-copied {
background: #059669;
}
.copy-button:disabled {
cursor: default;
opacity: 0.9;
}
.copy-status {
min-height: 24px;
margin: 16px 0 0;
color: #047857;
font-size: 0.9rem;
font-weight: 600;
}
@media (max-width: 520px) {
body {
padding: 16px;
}
.copy-card {
padding: 20px;
border-radius: 16px;
}
.copy-item {
grid-template-columns: 1fr;
}
.copy-button {
width: 100%;
}
}
</style>
</head>
<body>
<main class="copy-card">
<span class="copy-card__badge">Quick Copy</span>
<h1>Useful information</h1>
<p class="copy-card__intro">
Copy any value below with one click.
</p>
<div class="copy-list">
<div class="copy-item">
<div class="copy-item__content">
<span class="copy-item__label">Email address</span>
<p id="emailAddress" class="copy-item__value">user@example.com</p>
</div>
<button
class="copy-button"
type="button"
data-copy="#emailAddress"
data-copy-message="Email copied!"
>
Copy
</button>
</div>
<div class="copy-item">
<div class="copy-item__content">
<span class="copy-item__label">Referral code</span>
<code id="referralCode" class="copy-item__value">WELCOME25</code>
</div>
<button
class="copy-button"
type="button"
data-copy="#referralCode"
data-copy-message="Referral code copied!"
>
Copy
</button>
</div>
<div class="copy-item">
<div class="copy-item__content">
<span class="copy-item__label">Share link</span>
<input
id="shareLink"
class="copy-item__value"
type="text"
value="https://example.com/share/abc123"
readonly
style="width: 100%; border: 0; padding: 0; background: transparent; outline: none;"
>
</div>
<button
class="copy-button"
type="button"
data-copy="#shareLink"
data-copy-message="Link copied!"
>
Copy
</button>
</div>
</div>
<p id="copyStatus" class="copy-status" aria-live="polite"></p>
</main>
<script>
function copyToClipboard(text) {
if (navigator.clipboard && window.isSecureContext) {
return navigator.clipboard.writeText(text);
}
return new Promise((resolve, reject) => {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.setAttribute("readonly", "");
textarea.style.position = "fixed";
textarea.style.left = "-9999px";
textarea.style.opacity = "0";
document.body.appendChild(textarea);
textarea.select();
textarea.setSelectionRange(0, textarea.value.length);
try {
const successful = document.execCommand("copy");
if (!successful) {
throw new Error("Copy command was unsuccessful.");
}
resolve();
} catch (error) {
reject(error);
} finally {
textarea.remove();
}
});
}
function getCopyText(target) {
if (
target instanceof HTMLInputElement ||
target instanceof HTMLTextAreaElement
) {
return target.value;
}
return target.textContent.trim();
}
function showCopyFeedback(button, message) {
const status = document.querySelector("#copyStatus");
const originalText = button.textContent.trim();
button.textContent = "✓ Copied";
button.classList.add("is-copied");
button.disabled = true;
status.textContent = `✓ ${message}`;
window.setTimeout(() => {
button.textContent = originalText;
button.classList.remove("is-copied");
button.disabled = false;
status.textContent = "";
}, 2000);
}
document.addEventListener("click", async (event) => {
const button = event.target.closest("[data-copy]");
if (!button) {
return;
}
const targetSelector = button.getAttribute("data-copy");
const target = document.querySelector(targetSelector);
if (!target) {
console.error(`Copy target not found: ${targetSelector}`);
return;
}
const text = getCopyText(target);
const message = button.dataset.copyMessage || "Copied to clipboard!";
try {
await copyToClipboard(text);
showCopyFeedback(button, message);
} catch (error) {
console.error("Unable to copy text:", error);
const status = document.querySelector("#copyStatus");
status.textContent = "Copy failed. Please copy the value manually.";
}
});
</script>
</body>
</html>
How it works
- Each copy button stores the selector of its target element inside a
data-copyattribute. - The delegated click listener finds the nearest element containing
data-copy. - The script finds the associated paragraph, code element, input, or textarea.
getCopyText()reads eithervalueortextContentdepending on the target element.copyToClipboard()uses the modern Clipboard API when available.- A temporary textarea provides a fallback when the Clipboard API cannot be used.
- After a successful copy, the button changes to
✓ Copiedand the status area announces the result. - After two seconds, the button returns to its normal state.
Usage example
You can add more copyable values by following the same pattern. No package installation is required.
<div class="copy-item">
<div class="copy-item__content">
<span class="copy-item__label">Coupon code</span>
<code id="couponCode" class="copy-item__value">SAVE20</code>
</div>
<button
class="copy-button"
type="button"
data-copy="#couponCode"
data-copy-message="Coupon code copied!"
>
Copy
</button>
</div>
Customization notes
- Change the card title and labels to match your project.
- Replace the example email, referral code, and link with real values.
- Change
data-copy-messageto customize the success message for each button. - Adjust the border radius, spacing, shadows, and typography to match your application's design system.
- Change the
.is-copiedstate if you want a different visual success style. - Add SVG icons inside the buttons for copy and success states.
- Reuse the same JavaScript with dynamically generated elements because the click listener uses event delegation.
Using only the JavaScript
The visual styles are optional. If your project already has its own UI system, keep the JavaScript behavior and replace the included CSS with your own classes.
Common mistakes
- Thinking an example value is something to install: values such as
WELCOME25are only text being copied. This snippet does not require npm or any external dependency. - Using the Clipboard API outside a secure context: clipboard access generally works on HTTPS pages and localhost.
- Using
textContentfor form fields: inputs and textareas store their current content invalue. - Using
innerHTMLwhen copying plain text: this can copy unwanted markup. PrefertextContent. - Not checking whether the target exists: an invalid selector or missing element can break the copy workflow.
- Ignoring clipboard errors: browser permissions or restrictions can cause copy operations to fail.
- Removing keyboard focus styles: always preserve a visible
:focus-visiblestate for keyboard users. - Designing only for desktop: copy rows should stack cleanly on narrow screens.
Related practice ideas
- Build a coupon-code card with a countdown timer and copy button.
- Create a referral-link component with a share button.
- Add copy buttons to code blocks in a documentation website.
- Create a reusable JavaScript
CopyButtoncomponent. - Add toast notifications instead of the inline success message.
- Add SVG icons that switch from a clipboard icon to a checkmark after copying.
- Create a password or API-key field with Show, Hide, and Copy controls.
- Extend the component to support dynamically generated values.