More Buttons Snippets
Copy Button — Free HTML CSS JS Clipboard Snippet
Copy to Clipboard Button · Buttons · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Copy Button — Clipboard API, SVG Icon Swap, Timeout Reset & Token Row Pattern

A copy to clipboard button is one of the most-used micro-interactions in developer-facing products. Any interface that displays a code snippet, API key, access token, webhook URL, terminal command, or share link needs a one-click copy mechanism — requiring users to manually select and copy text causes unnecessary friction and increases the chance of copying errors. This snippet implements two common copy button patterns: an icon button inside a dark code block, and inline text buttons in a credential list.
The Clipboard API and async promise handling
navigator.clipboard.writeText(text) is the modern Clipboard API. It's asynchronous — it returns a Promise that resolves when the text is successfully written to the clipboard. The .catch(() => {}) handles the case where clipboard access is denied — this happens in non-HTTPS contexts (except localhost), in browser privacy modes, or when the user has denied clipboard permission. For maximum compatibility, a fallback using document.execCommand('copy') can be placed inside the catch block: create a temporary textarea, set its value, select it, call execCommand, then remove it.
The SVG icon swap visual feedback
The copy button contains two SVG icons: a copy icon (.icon-copy) and a checkmark icon (.icon-check). In the default state, .icon-check { display: none }. When .copied is added: .copy-btn.copied .icon-copy { display: none } and .copy-btn.copied .icon-check { display: block }. This pure CSS icon swap requires no DOM manipulation — just a class toggle. Simultaneously, .copy-btn.copied applies a green background tint (rgba(74,222,128,0.15)) and a green border, providing a clear colour-coded confirmation state.
The setTimeout reset cycle
copyCode() calls btn.classList.add('copied') immediately on click, then setTimeout(() => btn.classList.remove('copied'), 2000) schedules the reset 2 seconds later. The 2-second window gives the user enough time to notice the confirmation feedback while keeping the button ready for repeated copying. If a user clicks copy again within the 2 seconds, the existing timeout is still active — the next call to classList.add adds the class that is already present (no visual change) and sets a new 2-second timeout, effectively extending the confirmation display.
The token row pattern
The second part of the demo shows a different copy pattern: small inline text buttons in a credential list. The copyText(btn, text) function takes the button reference and the text value directly. On click, it sets btn.textContent = '✓ Copied' and adds the .ok class for green styling, then resets after 2 seconds. This pattern is used in API key dashboards, environment variable panels, and developer settings pages where multiple copyable values are shown in a list.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to dig through the Clipboard API docs to know exactly when writeText can silently fail here. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why the catch block is empty rather than showing an error state, and why the icon swap between the copy and check SVGs needs no JavaScript beyond one classList.add call. The same assistant can help optimize it — for instance asking whether repeated rapid clicks correctly extend the two-second confirmation window given how the existing setTimeout is scheduled, or whether that could leave a stale timeout resetting the class early. It's also useful for extending the pattern: ask it to add a real document.execCommand fallback inside the catch block for non-HTTPS contexts, generalize copyCode to accept any target element ID so multiple independent code blocks on one page each get correct behavior, or surface a visible error state when the clipboard write is actually denied instead of failing silently. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a copy-to-clipboard button in plain HTML, CSS, and JavaScript using the async Clipboard API, in two variants (an icon button and an inline text button) — no clipboard library, no frameworks.
Requirements:
- An icon-based copy button placed inside a code block that reads the exact visible text content of the code element (not innerHTML, so no markup leaks into the copied value) and writes it to the clipboard via the Clipboard API's async write method, silently handling the case where that call is rejected (e.g. denied permission or non-secure context).
- The button must contain two SVG icons, a default copy icon and a checkmark icon, where only CSS visibility rules driven by a single toggled class control which one is shown — no direct style manipulation of the icons in JavaScript.
- Clicking the button must add that class immediately (synchronously, not waiting for the clipboard write to resolve) so the user gets instant visual feedback, and a timer must remove the class automatically after a couple of seconds to return to the default state.
- Ensure that clicking the button again while the confirmation state is still showing correctly resets and restarts that timer rather than leaving two competing timers that could reset the class early.
- A second, separate copy pattern: several rows of labeled credential values (like an API key and a webhook URL) each with their own inline "Copy" text button that copies that row's specific value (passed directly to the handler, not read from a shared DOM element) and temporarily swaps its own label text and styling to a confirmed state before reverting.
- Structure the code so that adding the same copy behavior to additional, independent code blocks on the same page requires passing a target identifier rather than duplicating the function.Step by step
How to Use
- 1Click the copy icon button in the code blockClick the copy icon button in the top-right of the dark code block. The icon immediately swaps from a copy icon to a green checkmark, and the button background changes to a green tint. After 2 seconds, it resets to the default copy icon state ready for the next click.
- 2Click the small Copy text buttons in the token listClick the Copy button next to the API Key or Webhook URL rows in the credential list below the code block. The button text changes to '✓ Copied' and turns green. After 2 seconds it resets. This demonstrates the inline text-button copy pattern used in API dashboard and credentials pages.
- 3Update the code block content to your own snippetIn the HTML panel, change the text inside the <pre id='code'> element to your own terminal command, code snippet, or configuration text. The copyCode() function reads pre.textContent, so whatever text you put in the pre element is what gets copied to the clipboard.
- 4Change the timeout duration for the Copied stateIn the JS panel, find the two setTimeout calls. Change the 2000 millisecond value to adjust how long the confirmation state displays. 1500ms feels snappier, 3000ms gives more time for users to read the confirmation. Use the same value in both setTimeout calls for consistency across both copy patterns.
- 5Add the copy button to multiple code blocks on a pageFor multiple independent copy buttons, refactor copyCode() to accept an element ID parameter: function copyCode(id) { const text = document.getElementById(id).textContent; navigator.clipboard.writeText(text).catch(()=>{}); const btn = document.querySelector('[data-target="' + id + '"]'); btn.classList.add('copied'); setTimeout(() => btn.classList.remove('copied'), 2000); }. Each button passes its target ID via onclick='copyCode("myCodeId")'.
- 6Export and integrate into your documentation or dashboardClick HTML for a standalone file, JSX for a React component with a useCopyButton() hook that manages the copied state, or Tailwind for a styled React version. The JSX export uses useState for the copied boolean and useEffect with a cleanup to handle unmounting during the timeout window.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
navigator.clipboard.writeText(text) is an asynchronous function that uses the Permissions API to request clipboard write access, then writes the text to the system clipboard. It returns a Promise that resolves with undefined on success. It rejects (and the .catch() fires) in three cases: the page is not served over HTTPS (except localhost), the browser's clipboard permission for the site is denied in browser settings, or the browser doesn't support the Clipboard API (very old browsers). For a fallback, inside the catch block: const ta = document.createElement('textarea'); ta.value = text; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta).
The button contains two SVG icons with classes .icon-copy and .icon-check. The default CSS hides the checkmark: .icon-check { display: none }. When .copied is added to the button, two CSS rules fire simultaneously: .copy-btn.copied .icon-copy { display: none } hides the copy icon, and .copy-btn.copied .icon-check { display: block } shows the checkmark. No JavaScript DOM manipulation is needed for the icon swap — just the single classList.add('copied') call, and CSS handles the rest. The button background and border also change via .copy-btn.copied { background: rgba(74,222,128,0.15); border-color: rgba(74,222,128,0.3); color: #4ade80 } for a green confirmation state.
Refactor copyCode() to accept a target element ID: function copyCode(id) { const text = document.getElementById(id).textContent; navigator.clipboard.writeText(text).catch(()=>{}); const btn = event.currentTarget; btn.classList.add('copied'); setTimeout(() => btn.classList.remove('copied'), 2000); }. Pass the code block ID from the onclick: onclick='copyCode("example-1")'. Each code block gets a unique ID, and each copy button targets its own block. For a React implementation, use a ref on the pre element and pass it to the click handler.
Yes, in production. navigator.clipboard.writeText() requires either HTTPS or the localhost origin. It will not work on HTTP pages in modern browsers. During development, localhost (including ports like localhost:3000) works without HTTPS. On HTTP production sites, the API throws a NotAllowedError. The document.execCommand('copy') fallback works on HTTP but is deprecated and may be removed from browsers in future. The standard solution is to serve your site over HTTPS — all major hosting platforms (Netlify, Vercel, Cloudflare) provide HTTPS automatically.