Back to Home
AstroJS code blocks
How to add code blocks to your current AstroJS project
3 min read

Getting Started

Note
This post is meant to be a way to create your own code formatter, however, this website does not utilize the codeblocks shown and relies on Expressive Code instead.

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.