I found a few examples online for creating a code block with syntax highlighting and a copy-to-clipboard button, but I couldn’t get them to work straight away. So I thought, “It can’t be that hard to make this myself.” After some trial and error, I came up with these two components: CodeBlock and CopyToClipboard. Here’s how they work together and what they do:
The CodeBlock component is responsible for:
CopyToClipboard button for easy copying of the code.code: The code snippet to display.language: The programming language of the code snippet for syntax highlighting.hljs.highlightElement method is called on the code element to apply syntax highlighting.felipec.css in this case) to style the highlighted code.html-react-parser library is used to safely parse HTML content in the code string, ensuring proper rendering of any HTML tags.useRef hook is used to get a reference to the <code> element.useEffect hook ensures the highlighting runs whenever the language prop changes.<div> containing the code block is styled with padding, margins, and a rounded-lg border for aesthetics.const CodeBlock = ({ code, language }: Props) => {
const codeRef = useRef<HTMLElement>(null); // Ref to access the <code> element
useEffect(() => {
if (codeRef.current) {
try {
hljs.highlightElement(codeRef.current); // Apply syntax highlighting
} catch (error) {
console.error(`Error highlighting code with language "${language}":`, error);
}
}
}, [language]); // Re-run highlighting if the language changes
return (
<div className='rounded-lg mr-11 ml-6 mt-6 mb-6 relative'>
{/* Copy-to-clipboard button */}
<CopyToClipboard content={parse(code) as string}></CopyToClipboard>
<pre>
<code ref={codeRef} className={`language-${language} rounded-2xl min-h-28`}>
{parse(code)} {/* Render the parsed code */}
</code>
</pre>
</div>
);
};
CopyToClipboard ComponentThe CopyToClipboard component handles the logic for:
content: The text content to copy to the clipboard.navigator.clipboard.writeText method is used to copy the content to the user’s clipboard.isCopied state tracks whether the content has been successfully copied.setTimeout resets isCopied to false after 1 second, so the “copied!” message disappears.const CopyToClipboard = ({ content }: CopyProp) => {
const [isCopied, setIsCopied] = useState(false); // Tracks if the content is copied
const copy = async (content: string) => {
try {
await navigator.clipboard.writeText(content); // Copy content to clipboard
setIsCopied(true); // Show "copied!" feedback
setTimeout(() => {
setIsCopied(false); // Reset feedback after 1 second
}, 1000);
} catch (err) {
console.error("Failed to copy text: ", err); // Handle copy errors
}
};
return (
<button
className={`w-6 h-6 absolute right-6 top-5 text-xs ${isCopied ? "text-zinc-400" : "text-zinc-500"}`}
onClick={() => copy(content)} // Call copy function on click
>
{/* SVG icon for clipboard */}
<svg width="100%" height="100%" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="..." stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
{isCopied ? "copied!" : "copy"} {/* Display feedback or default "copy" */}
</button>
);
};
CodeBlock component renders a code snippet with syntax highlighting using Highlight.js.CopyToClipboard component is added to the CodeBlock, positioned as a floating button on the top-right corner.content) to the clipboard and briefly shows a “copied!” message.