Copy Code Button
A real copy button on every code block on this site, added by one script, with no per-page setup.
Live Implementation
Hover the code block below and a copy button appears in the top-right corner. This isn't a special widget — it's the exact same script that put a copy button on every other code block on this entire site, including the "Important Files" section further down this page:
const greeting = "Copy me with the button above";
console.log(greeting); How It Works
One script, loaded sitewide through main.js, finds every <pre> on the page and wraps each one:
document.querySelectorAll("pre").forEach((pre) => {
const wrapper = document.createElement("div");
wrapper.className = "code-block-wrapper";
pre.parentNode.insertBefore(wrapper, pre);
wrapper.appendChild(pre);
// ...button gets appended to wrapper, not pre
}); The wrapper matters more than it looks. Code blocks scroll horizontally (overflow-x: auto), and a button positioned inside a scrolling element can get clipped or scroll away with the content. Putting the button in a sibling wrapper instead — positioned relative to the wrapper, not the pre — keeps it pinned in the corner regardless of how far the code scrolls.
Clicking the button copies pre’s real text content via navigator.clipboard.writeText(), swaps the icon to a checkmark, and swaps it back after two seconds:
await navigator.clipboard.writeText(code.innerText);
button.classList.add("is-copied");
button.innerHTML = CHECK_ICON;
setTimeout(() => {
button.classList.remove("is-copied");
button.innerHTML = COPY_ICON;
}, 2000); If navigator.clipboard doesn’t exist — an old browser, or a page loaded over plain HTTP instead of HTTPS — the whole script does nothing, on purpose. No button ever appears that wouldn’t actually work.
Folder Structure
src/assets/js/copy-code.js ← the whole feature, loaded sitewide via main.js
src/assets/scss/components/_code.scss ← .code-block-wrapper and .copy-code-button styles Important Files
src/assets/js/copy-code.js
const COPY_ICON = `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="8" y="8" width="12" height="12" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M6 16H5a1.5 1.5 0 0 1-1.5-1.5v-9A1.5 1.5 0 0 1 5 4h9A1.5 1.5 0 0 1 15.5 5.5V6" fill="none" stroke="currentColor" stroke-width="1.6"/></svg>`;
const CHECK_ICON = `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" d="M7.5 12.5l3 3 6-6.5"/></svg>`;
if (navigator.clipboard) {
document.querySelectorAll("pre").forEach((pre) => {
const wrapper = document.createElement("div");
wrapper.className = "code-block-wrapper";
pre.parentNode.insertBefore(wrapper, pre);
wrapper.appendChild(pre);
const button = document.createElement("button");
button.type = "button";
button.className = "copy-code-button";
button.setAttribute("aria-label", "Copy code");
button.innerHTML = COPY_ICON;
wrapper.appendChild(button);
button.addEventListener("click", async () => {
const code = pre.querySelector("code") || pre;
try {
await navigator.clipboard.writeText(code.innerText);
button.classList.add("is-copied");
button.setAttribute("aria-label", "Copied");
button.innerHTML = CHECK_ICON;
setTimeout(() => {
button.classList.remove("is-copied");
button.setAttribute("aria-label", "Copy code");
button.innerHTML = COPY_ICON;
}, 2000);
} catch {}
});
});
} Notes
- This applies to literally every
<pre>on the site, including the JSON dump on the Data Files demo and every “Important Files” snippet everywhere — not just fenced code blocks in Markdown. - The Clipboard API requires a secure context. It works on
localhostduring development and on the real deployed HTTPS site, but would silently do nothing if this were ever served over plain HTTP. - No dependency was added for this.
navigator.clipboardhas been in every evergreen browser for years — reaching for a library here would just be extra weight for something the platform already does.