Copy to Clipboard
Read-only field and Copy button: green check and "Copied!" when the copy works, a visible message and selected text when the browser refuses it.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Utilities has 43 effects, including 3 free. Explore the category →
3 usage examples



How it works
On click, the script copies the value of the .cpb-input field, or the text of data-cpb-text if it is set on the root: the field can then show a masked key while the real one goes to the clipboard. It first uses navigator.clipboard.writeText, available on a page served over https or localhost.
If that API is missing (http page) or the browser refuses, a fallback copies with document.execCommand('copy') through a hidden field, then gives focus back where it was. If the fallback fails too, nothing is hidden: the real text is put in the field, selected, and the data-cpb-fail message shows in amber under the row, asking to copy with the keyboard.
When the copy works, the button turns to a green gradient, the copy icon gives way to a bouncing check mark (0.4 s) and the label becomes data-cpb-done for data-cpb-reset milliseconds (2000 by default). The check mark sits where the icon was: the button keeps its width. The field is 260 px wide (--cpb-field-width), shrinks on a narrow screen and moves above the button when space runs out.
Each .fx-0739-root is a separate instance. The fx0739:copy event (detail.ok, detail.text) leaves the root on every try, and root.fx0739.copy() starts a copy from your code.
Accessibility
- Announcement: the
.cpb-msgarea (role="status") reads "Copied to clipboard" on success, and the failure message when copying is refused. Without it, a screen reader user would not know whether the click did anything. - Named field: the field has an
aria-label("Command to copy"); adapt it to what it holds. Read-only, it can still be reached with the keyboard and selected by hand. - Keyboard: real
<button>, Enter and Space; lavender ring on keyboard focus on the field and the button. On failure, focus moves to the field with the text selected, ready for Ctrl+C. - Contrast: white text on the indigo gradient
#5a5df0 → #8453f5: 4.6 to 4.9:1; on the green of "Copied!",#0c855d → #036043: 4.6:1 and above. Measured in Chromium, Firefox and WebKit, at rest, on hover and after copying. If you change these gradients, keep at least 4.5:1 on their lightest colour. - Reduced motion: no lift on hover, no transition and no check mark bounce; the colour and label change remain.
Browser compatibility
Dependency-free JavaScript (3.7 KB comments included) and two SVG icons. Measured in Chromium 151, Firefox 153 and WebKit 26.5 (Playwright): code pasted twice, each copy copies on its own (text read back from Chromium's clipboard), page without the clipboard API (fallback works), copy fully refused (visible message, text selected), keyboard, touch at 390 px, reduced motion, 0 errors, no name added to window.
navigator.clipboard.writeText exists since Chrome 66, Firefox 63 and Safari 13.1; before that, the execCommand('copy') fallback (Chrome 42, Firefox 41, Safari 10) takes over. The focus ring needs :focus-visible (Chrome 86, Firefox 85, Safari 15.4). Without JavaScript, the button copies nothing but the field can still be selected.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0739-root" data-cpb-done="Copied!" data-cpb-said="Copied to clipboard" data-cpb-fail="Automatic copy was blocked: the text is selected, copy it with Ctrl+C (⌘+C on Mac).">
<div class="cpb-demo">
<input type="text" class="cpb-input" value="npm run build" readonly aria-label="Command to copy">
<button type="button" class="cpb-btn">
<svg class="cpb-copy-icon" aria-hidden="true">…</svg>
<svg class="cpb-check-icon" aria-hidden="true">…</svg>
<span class="cpb-text">Copy</span>
</button>
<p class="cpb-msg" role="status" aria-live="polite"></p>
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Copied text (HTML — data-cpb-text on the root) |
absent: the field's value | Copies a different value from the one shown (masked key, full link behind a short label). |
Label after copying (HTML — data-cpb-done) |
"Copied!" | Replaces the button text during the copied state. |
Announcement (HTML — data-cpb-said) |
"Copied to clipboard" | Sentence read by screen readers; say what was copied. |
Failure message (HTML — data-cpb-fail) |
"Automatic copy was blocked: the text is selected…" | Shown in amber under the row and announced when every copy attempt failed. |
Copied state duration (HTML — data-cpb-reset) |
2000 ms | Time before the starting label comes back. |
Size (CSS — --cpb-font-size, --cpb-field-width) |
1rem · 260 px | The font size drives paddings and icons; the field width is set separately. |
Frame (CSS — --cpb-height, --cpb-page, --cpb-panel, --cpb-pad) |
300 px · #0a0a0f · #1a1a2e · 20 px |
Set 0 and transparent to put the field and button in your own card. |
Code (JS — root.fx0739.copy(), fx0739:copy event) |
— | Starts a copy; the event says whether it worked (detail.ok), for your analytics for instance. |
FAQ
navigator.clipboard to secure pages (https or localhost). Over http, the effect tries execCommand('copy'), which still works almost everywhere after a click; otherwise the text is selected and the message asks for Ctrl+C. Moving the site to https settles it.data-cpb-text value is then put in the field and selected. If it must never be shown, remove that behaviour in fail() and keep only the message..fx-0739-root block has its own field, button, timer and announcement. The script can even be pasted twice without copying twice.