DEV Community

Cover image for Add a 'copy code to clipboard' button to your blog's code blocks
Rob OLeary
Rob OLeary

Posted on • Updated on • Originally published at roboleary.net

Add a 'copy code to clipboard' button to your blog's code blocks

If you are writing about code, you are likely to include some code blocks to complement what it is you are discussing or explaining. To improve the experience for the reader, you can consider highlighting the syntax to make it easier to read, and adding a "copy code" button to make it simply for them to copy and paste the code (a key developer skill)! I will show you to do the latter.

A simple code example

We only want to add our button to code blocks that are wrapped inside a pre such as below:

<pre><code class="language-css">.some-box {
    width: 20px;
    height: 20px;
    background: black;
    margin-bottom: 1.5rem;
}
</code></pre>
Enter fullscreen mode Exit fullscreen mode

In this example, I will use the syntax highlighting library Prism (this is used in the static site generator Eleventy also). According to the HTML5 spec, the recommended way to define the language for a code block is a through a language-xxxx class i.e. language-css for CSS code. Prism looks for this class to identify blocks to add highlighting to. Most syntax highlighting libraries follow a similar convention.

Prism adds the language-xxxx class from the code element to the pre for you. This will make it easier for you to target the correct elements in your CSS later.

Here is the codepen of what I will cover below.

CSS

We want to position the button absolutely in the top right corner of the pre. In our JavaScript code, we will add the button as a child of the pre element. To achieve this placement, we set the pre as position: relative and the button as position: absolute, and set the top and right properties of the button.

Also, we want to add enough top padding to the pre to make space for the button, so that the text will never be covered by the button.

pre[class*="language-"] {
  position:relative;
  margin:5px;
  padding:2rem .5rem .5rem .5rem;

  /* more stuff */
}

button{
  position:absolute;
  top:4px;
  right:4px;

  /* more stuff */
}
Enter fullscreen mode Exit fullscreen mode

You can place the button somewhere else, and style it differently if you prefer.

JavaScript

We will only want to target the pre elements that contain a code element. If you are using a static site generator or JavaScript library for syntax highlighting, usually there is a class added to the pre like I demonstrated with Prism earlier. If you don't have a class available, it is tricker to target the right elements since a query selection along the lines of "find me all pre elements that contain a code element" is not possible with querySelectorAll(), because there is no parent selector available in CSS syntax. You will need to write some extra JavaScript code to get a reference to these elements.

Writing to the system clipboard is quite straightforward. There is a browser API, the Clipboard API, which enables you to asynchronously read from and write to the system clipboard. The browser support is excellent (for writing to the clipboard). It is recommended that you use the Clipboard API instead of the deprecated document.execCommand() method.

To access the clipboard, you use the navigator.clipboard global. To write to the clipboard there is an async writeText() function.

await navigator.clipboard.writeText("some text");
Enter fullscreen mode Exit fullscreen mode

How do we get the text of the code element?

In the button click event handler, we need to get the element indirectly. We need to get the parent element of the button (the pre), and then execute querySelector("code") on it to get a reference the child code element. Then, we can get text through the element's innerText property. This kind of hokey-pokey is required when working with the DOM sometimes!

Let's put it all together then.

const copyButtonLabel = "Copy Code";

// You can use a class selector instead if available. 
let blocks = document.querySelectorAll("pre");

blocks.forEach((block) => {
  // only add button if browser supports Clipboard API
  if (navigator.clipboard) {
    let button = document.createElement("button");
    button.innerText = copyButtonLabel;
    button.addEventListener("click", copyCode);
    block.appendChild(button);
  }
});

async function copyCode(event) {
  const button = event.srcElement;
  const pre = button.parentElement;
  let code = pre.querySelector("code");
  let text = code.innerText;
  await navigator.clipboard.writeText(text);
}

Enter fullscreen mode Exit fullscreen mode

If you use a client-side syntax highlighting library and rely on it adding a class to the pre, keep in mind that you need to wait until it is finished first before you run the above code. These days it is more common that this work is done by the static site generator at build time.

While the above code works perfectly, we don't give any feedback to the user that the code was copied. So it might be nice to do something to indicate to the user that the task was completed successfully.

Adding feedback

The first thing that popped into my head was to change the button text to "Code Copied" when the action is done, and reset it to "Copy Code" after a second through setTimeout().

  button.innerText = "Code Copied";

  setTimeout(()=> {
    button.innerText = copyButtonLabel;
  },1000)
Enter fullscreen mode Exit fullscreen mode

If you don't like the fact that the button grows in size when the text is switched, you can set a min-width equal to the width of the button with the longer text.

I think that this is good enough, but you could add a toast notification, or an animation of some sort instead if you want to do something fancier.

Discussion (3)

Collapse
robole profile image
Rob OLeary Author

copy and paste button

Collapse
radualexandrub profile image
Radu-Alexandru B

Yes pretty much. I've mapped copy+paste as macros:
Copy Paste Macros

Collapse
robole profile image
Rob OLeary Author

copy loof