How to add a copy code button to your blog posts

May 20, 2021 - 4 min read

How I added copy code button to my blog posts

I recently revamped my site and I was looking for a way how to do my own copy code button for code snippets. I wanted something clean, simple and provides user with feedback.

After a bit of consulting the main man google, I could not find any good examples using a similar tech stack. So back to the drawing board.

Then I realised that I could make my own custom pre component.

In this example I am going to be using:

Here is a good starting point if you would like to do the same.


Let's start with this.

export const Pre = ({children, ...props}) => { return { <pre {...props}>{children}<pre> } }

Then add that to my MDXComponents.

import {Pre} from './Pre' export const MDXComponents = { pre: Pre, }

Then pass the custom components into the MDXRemote.

import {MDXRemote} from 'next-mdx-remote' import {MDXComponents} from './MDXComponents' export const Layout = ({mdxSource}) => { return ( <MDXRemote {...mdxSource} components={MDXComponents} /> ) }

Right now, you won't notice any difference as we have only replicated the default behaviour.


We want the copy button to be top right even when the pre will overflow, so we will start by wrapping the pre in a div.

<div className="relative"> ... </div>

Then create a div inside our pre and position that top right

<div className="absolute flex items-center space-x-2 top-0 right-0 m-2"> <button type="button" aria-label="Copy to Clipboard" className="hidden transition bg-transparent border rounded-md p-2 focus:outline-none fade-in group-hover:flex" > <svg xmlns="" className="h-4 w-4 pointer-events-none" fill="none" viewBox="0 0 24 24" stroke="currentColor" > <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7v8a2 2 0 002 2h6M8 7V5a2 2 0 012-2h4.586a1 1 0 01.707.293l4.414 4.414a1 1 0 01.293.707V15a2 2 0 01-2 2h-2M8 7H6a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2v-2" /> </svg> </button> </div>

So we can prove the user with some feedback lets add some state and add an onClick={onClick} event to the button.

const [copied, setCopied] = useState(false) const onClick = () => { // Copy to clipboard here setCopied(true) }

In my case I also want to reset the user feedback after 2 seconds.

useEffect(() => { const timer = setTimeout(() => setCopied(false), 2000) return () => clearTimeout(timer) }, [copied])

Now we need to implement some way of copying text into the clipboard. I originally thought of using react-copy-to-clipboard but I don't have access to the text only the react components.

I manged to come up with a nice work around by using ref and innerText.

First we need to create a ref const preRef = useRef(null) then hook that up to the pre element.

Let's create our custom copy to clipboard function. I wanted to support newer writeText API and a fall back just in case it was not supported.

export const copyToClipboard = (text) => { return new Promise((resolve, reject) => { if (navigator?.clipboard) { const cb = navigator.clipboard cb.writeText(text).then(resolve).catch(reject) } else { try { const body = document.querySelector('body') const textarea = document.createElement('textarea') body?.appendChild(textarea) textarea.value = text document.execCommand('copy') body?.removeChild(textarea) resolve() } catch (e) { reject(e) } } }) }

Now let's add that to our onClick event.

const onClick = () => { copyToClipboard(preRef.current.innerText) setCopied(true) }

After that you should have something useable, then you can add styling based on state etc.

You can find my full code here


Creating your own copy code button is a great way to customize it and adds a better user experience. I hope with my examples above using MDXRemote and Tailwind, you can create something for your own blog.

Happy coding! 👋