Getting Started

You want to first install Lit into your current Astro project. You can do this with

terminal
npm i lit

Creating JS files

In your components folder, create a new folder named copy-code (or whatever name you prefer.) Create these files in the folder you just created and paste the following code:

copy-code/copy-code-button.js
import { LitElement, html, css } from "lit";
export const copyIcon = html`<svg
xmlns="http://www.w3.org/2000/svg"
height="18px"
viewBox="0 0 24 24"
width="18px"
fill="currentColor"
>
<path d="M0 0h24v24H0z" fill="none" />
<path
d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"
/>
</svg>`;
export const checkIcon = html`<svg
xmlns="http://www.w3.org/2000/svg"
height="18px"
viewBox="0 0 24 24"
width="18px"
fill="#00FF00"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
</svg>`;
export class CopyCodeButton extends LitElement {
static styles = [
css`
:host {
display: inline-flex;
position: absolute;
top: 0.5rem;
right: 0.5rem;
}
button {
display: inline-flex;
align-items: center;
justify-content: center;
width: fit-content;
margin: 0;
padding: 0.5rem;
background: #e2e8f022;
border: none;
border-radius: 0.25rem;
color: #fff;
cursor: pointer;
font-weight: 600;
}
@keyframes animateScale {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
button.clicked svg {
animation: animateScale 0.5s ease;
}
`,
];
constructor() {
super();
this._isCopied = false;
}
static get properties() {
return {
_isCopied: { type: Boolean },
};
}
copyCode() {
this._isCopied = true;
const pre = this.parentElement;
let code = pre.querySelector("code");
const range = document.createRange();
range.selectNode(code);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
// check if the browser supports clipboard API
if (!navigator.clipboard) {
// if not use the old commandExec() way
document.execCommand("copy");
} else {
try {
navigator.clipboard.writeText(range.toString());
} catch (error) {
console.error(error);
}
}
window.getSelection().removeAllRanges();
setTimeout(() => {
this._isCopied = false;
}, 1000);
const button = this.shadowRoot.querySelector("button");
button.classList.add("clicked");
setTimeout(() => {
this._isCopied = false;
button.classList.remove("clicked");
}, 1000);
}
render() {
return html`
<button @click=${this.copyCode}>
${this._isCopied ? checkIcon : copyIcon}
</button>
`;
}
}
customElements.define("copy-code-button", CopyCodeButton);

copy-code/create-copy-buttons.js
let blocks = document.querySelectorAll("pre");
blocks.forEach((block) => {
let codeElement = block.querySelector("code");
let containerDiv = document.createElement("div");
containerDiv.classList.add("code-container");
containerDiv.appendChild(codeElement);
block.appendChild(containerDiv);
let button = document.createElement("copy-code-button");
containerDiv.appendChild(button); // Append button to the containerDiv, not block
});

copy-code/styles.css
pre[class*="language-"] {
overflow: auto;
}
pre {
position: relative;
}
.code-container {
position: relative;
overflow-x: auto;
max-width: 100%;
}
pre code {
max-height: 300px;
overflow-y: auto;
display: block;
}
copy-code-button {
position: absolute;
top: 5px;
right: 10px;
}
pre code::-webkit-scrollbar-corner {
background-color: transparent;
}
pre code::-webkit-scrollbar {
width: 2px;
height: 2px;
background-color: transparent;
}
pre code::-webkit-scrollbar-track {
background-color: transparent;
}
pre code.dark::-webkit-scrollbar-thumb {
border-radius: 8px;
background-color: #363636;
}
pre code.dark::-webkit-scrollbar-thumb:hover {
background-color: #555;
}
pre code::-webkit-scrollbar-thumb {
border-radius: 8px;
background-color: #c1c1c1;
}
pre code::-webkit-scrollbar-thumb:hover {
background-color: #a0a0a0;
}
pre code {
scrollbar-width: thin;
scrollbar-color: #363636 transparent;
}
pre code.dark::-moz-scrollbar-thumb {
border-radius: 8px;
background-color: #363636;
}
pre code.dark::-moz-scrollbar-thumb:hover {
background-color: #555;
}
pre code::-moz-scrollbar {
width: 5px;
background-color: transparent;
}
pre code::-moz-scrollbar-track {
background-color: transparent;
}
pre code::-moz-scrollbar-thumb {
border-radius: 8px;
background-color: #c1c1c1;
}
pre code::-moz-scrollbar-thumb:hover {
background-color: #a0a0a0;
}
pre code {
scrollbar-width: 0;
-ms-overflow-style: none;
overflow: -moz-scrollbars-none;
}
pre code.dark {
scrollbar-width: thin;
-ms-overflow-style: auto;
overflow: overlay;
}

In your layouts folder, go to the main layout and add these lines of code:

Layout.astro
---
import "../styles/globals.scss";
---
<head>
<script>
import "@/components/copy-code/copy-code-button.js";
import "@/components/copy-code/create-copy-buttons.js";
</script>
</head>

And you’re basically all set! Just refresh the page and a copy button should appear on all your code blocks!

Acknowledgements:

This guide was inspired by Jaydan Urwin. However, his code had a couple of bugs with how the copy button was positioned (during overflow). This code fixes that as well as provide scrollbars when an overflow occurs.