npm i rare-scripts
# or
pnpm add rare-scripts
This is a small utility that adds a “copy” content_copy icon next to things people actually want to copy. Links. Addresses. Code snippets. Anything.
Click the icon, the data goes to the clipboard, the icon briefly turns into a checkmark, everyone is happy and moves on.
The script doesn’t care about your layout, your CSS philosophy, or how deeply nested your markup is. It follows one simple rule: either you explicitly tell it what to copy, or you place it next to something marked as copyable.
That’s the whole contract:
- The script hooks clicks via the
rd-js-copyclass;copy-data-iconstays as the presentational class (hooks are never styled). - The copy glyph is baked into the CSS (
content_copy), so your markup needs nodata-icon— just<button class="copy-data-icon rd-js-copy"></button>. The script only setsdata-icon="check"on success, then removes it. - The button needs no
titleeither: the script labels bare icon-only buttons witharia-label="Copy"for screen readers (any name you provide is kept). - If the icon has
data-copy-target, the script copies text from that element. - If not, it looks for the nearest element marked with
data-copy. - If that element is a link, it copies the URL. Otherwise, it copies the text content.
No duplicated data. No hard-coded containers. No assumptions about structure. It works the same way on documentation sites, dashboards, crypto wallets, and boring corporate pages.
This is intentional. The script stays dumb. The HTML stays expressive.
Basic usage
Copy a link (URL, not label)
<a href="https://cdn.jsdelivr.net/script.min.js" data-copy>Copy Link</a>
<button class="copy-data-icon rd-js-copy"></button>
Clicking the icon copies the full CDN URL.
Check it out: Copy Link
Copy plain text (addresses, hashes, IDs)
<span data-copy>0x9f1b...cA11</span>
<button class="copy-data-icon rd-js-copy"></button>
Works exactly like wallet address copy buttons. Because that’s what it is.
Copy code from a block
Use the .code-block wrapper and a button with the copy-data-icon rd-js-copy classes to create a classic code block with a copy button:
<div class="code-block">
<button class="copy-data-icon rd-js-copy"></button>
</div>
To get a result like this:
Use data-copy-target when proximity is not enough or would be ambiguous.
JavaScript
Include once per page:
const ICON_SUCCESS = "check";
const RESET_MS = 1200;
function getCopyText(icon) {
if (icon.dataset.copyTarget) {
const target = document.querySelector(icon.dataset.copyTarget);
return target?.textContent?.trim() || "";
}
const source =
icon.closest("[data-copy]") ||
icon.parentElement?.querySelector("[data-copy]");
if (!source) return "";
return source.getAttribute("href") || source.textContent.trim();
}
function fallbackCopy(text) {
const ta = document.createElement("textarea");
ta.value = text;
ta.style.position = "fixed";
ta.style.opacity = "0";
ta.style.pointerEvents = "none";
document.body.appendChild(ta);
ta.select();
const ok = document.execCommand("copy");
ta.remove();
return ok;
}
document.addEventListener("click", async (e) => {
const icon = e.target.closest(".rd-js-copy");
if (!icon) return;
if (icon.dataset.copyBusy) return;
const text = getCopyText(icon);
if (!text) return;
icon.dataset.copyBusy = "1";
const showSuccess = () => {
icon.dataset.icon = ICON_SUCCESS;
clearTimeout(icon._copyTimer);
icon._copyTimer = setTimeout(() => {
delete icon.dataset.icon;
delete icon.dataset.copyBusy;
}, RESET_MS);
};
try {
await navigator.clipboard.writeText(text);
showSuccess();
} catch {
if (fallbackCopy(text)) showSuccess();
else delete icon.dataset.copyBusy;
}
});
// Icon-only buttons render just a CSS glyph, so give any bare .rd-js-copy an
// accessible name (author-provided aria-label / title / text is respected).
function labelCopyIcons() {
document.querySelectorAll(".rd-js-copy").forEach((icon) => {
if (!icon.getAttribute("aria-label") && !icon.getAttribute("title") && !icon.textContent.trim()) {
icon.setAttribute("aria-label", "Copy");
}
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", labelCopyIcons);
} else {
labelCopyIcons();
}
Including the script
Don’t forget to actually include the script on the page. Icons don’t copy things by positive thinking alone.
You can either host the file locally or load it from a CDN. Both options work the same way.
// Local file:
<script src="/js/copy-to-clipboard.min.js"></script>
// CDN:
<script src="https://cdn.jsdelivr.net/gh/raredigits/rare-scripts@v3.1.0/copy-to-clipboard/copy-to-clipboard.min.js"></script>
⚠️ Performance tip: For production, download the script and host it on your own server rather than loading it from a CDN.
Include it once per page, preferably near the end of the document or with defer.
Styling
Make it feel clickable, not decorative:
.copy-data-icon {
display: inline-block;
font-family: "Material Symbols Outlined";
position: relative;
top: 0.2em;
font-size: inherit;
line-height: inherit;
background: none;
color: var(--copy-icon-color, var(--text-color-light));
border: none;
padding: 0;
cursor: pointer;
}
.copy-data-icon::before {
content: "content_copy"; /* default glyph — markup needs no data-icon */
}
.copy-data-icon[data-icon]::before {
content: attr(data-icon); /* the script sets data-icon="check" on success */
}
.copy-data-icon:hover {
color: var(--copy-icon-color-hover, var(--primary-color));
background: none;
border: none;
}
/* Optional: inverted icon for dark surfaces (put on the icon or any ancestor) */
.copy-data-icon-inverted {
--copy-icon-color: #fafafa;
--copy-icon-color-hover: #ccc;
}
Recoloring
The copy icon defaults to a muted gray (--text-color-light), which can fade into some surfaces or clash with a brand. Both of its colors are exposed as component tokens — --copy-icon-color and --copy-icon-color-hover — so any container (or a single icon) can override them without fighting the selector. Set the token on whatever wraps the icon (here, a code block — its copy button is already pinned top-right by .code-block):
<!-- recolor every copy icon inside this block -->
<div class="code-block" style="--copy-icon-color: var(--red);">
<pre><code data-copy>0x9f1b...cA11</code></pre>
<button class="copy-data-icon rd-js-copy"></button>
</div>
Live — the exact snippet above, rendered. The icon is red via the token; its top-right position comes from .code-block itself, so there is nothing extra to position:
0x9f1b...cA11
Shortcut for dark surfaces. The most common recolor — a light icon so it stays visible on a dark background — ships as a ready-made class, .copy-data-icon-inverted. It just sets the two tokens (--gray-lightest / --gray), so put it on the icon or any ancestor instead of writing the inline style:
<!-- on the icon -->
<button class="copy-data-icon rd-js-copy copy-data-icon-inverted"></button>
<!-- or on a container, to invert every copy icon inside -->
<div class="dark-panel copy-data-icon-inverted"> … </div>
Live — the inverted icon on a dark panel (light glyph, still visible), next to the default one for contrast:
Design principles (why this won’t rot):
- The script does not depend on layout.
- HTML owns the meaning. JavaScript only moves data.
- No duplicated content in attributes.
- Works with links, text, code, addresses.
- Scales from one icon to hundreds without extra listeners.
If an element should be copied, mark it with data-copy.
If the icon should copy something specific, tell it where with data-copy-target.
Everything else is none of the script’s business.
Changelog
v3.1.0
data-iconis now optional: the defaultcontent_copyglyph is baked into.copy-data-iconin CSS, and the script removesdata-iconon reset instead of writing it back. Markup drops to<button class="copy-data-icon rd-js-copy"></button>. Backward compatible — existingdata-icon="content_copy"markup still works.- The script now gives icon-only copy buttons an accessible name (
aria-label="Copy") if they have none, so markup needs notitleeither. Author-providedaria-label/titleis respected. - Added the
.copy-data-icon-invertedhelper class (light icon for dark surfaces) — put it on the icon or any ancestor instead of inlining the recolor tokens.
v3.0.0
- Breaking: click hook moved to the
rd-jscontract — the script listens for.rd-js-copy;.copy-data-iconis presentational only and no longer read by the script - Payload API unchanged:
data-copy,data-copy-target,data-iconall work as before - Icon colors are tokenized:
--copy-icon-color/--copy-icon-color-hoveroverride the defaults per container or per icon (CSS-only change, shipped with Rare Stylesv0.6.17)
v2.0.0
- Changed required selector to
.copy-data-icon, legacy markup no longer supported - Updated CSS to render icons via:
: beforewithcontent: attr (data-icon) - Replaced Material Icons
<span>integration with a generic<button>element - Introduced
data-iconattribute for icon state control instead of inner text - Improved clipboard API handling; deprecated fallback kept as backup