If you’ve ever scrolled through a well-documented developer blog or API guide, you know the joy of stumbling upon beautifully styled code blocks. They’re not just a feast for the eyes but also a crucial part of any developer-facing content, ensuring clarity and engagement. In this article, we’ll build custom, sleek code blocks with single-tab and multi-tab functionality using modern web technologies. And yes, there’s a pinch of humor sprinkled along the way—because debugging CSS deserves some levity.
print("Inside Custom Code Block!")
You might wonder, "Why reinvent the wheel when tools like Prism.js or Highlight.js exist?" Well, here’s why:
Building your own also means you won’t accidentally spend an hour wondering why Prism.js refuses to highlight your .tsx
file correctly (we’ve all been there).
This guide assumes you have a working knowledge of:
Let’s dive into the key components.
.codeblock-container
The CSS is where the real magic happens. With TailwindCSS’s utility-first approach, crafting a visually appealing code block is a breeze (and no, Tailwind didn’t pay me to say this).
Here’s how we make line numbers and spacing shine:
.codeblock-container {
pre .monokai {
@apply !px-0 !rounded-none;
}
code {
@apply flex flex-col;
}
[data-highlighted-line] {
@apply bg-blue-600/15 overflow-clip relative;
position: relative;
}
[data-highlighted-line]::before {
@apply absolute border-l-[5px] !pl-1 border-blue-700/80;
}
[data-rehype-pretty-code-fragment] {
@apply !my-0 border border-background-tertiary overflow-y-hidden rounded-b-md;
}
[data-line] {
@apply pl-8 -ml-1 min-h-6;
position: relative;
counter-increment: line-number;
}
[data-line]::before {
@apply pl-2 inline-flex;
content: counter(line-number);
position: absolute;
left: 0;
color: gray;
font-size: 0.875rem;
}
}
CheckIcon
and CopyIcon
Icons are the unsung heroes of UI. They’re tiny but mighty, and we’re making them reusable with TypeScript and Framer Motion for a touch of flair.
import { motion } from "framer-motion";
interface IconProps extends React.SVGProps<SVGSVGElement> {
className?: string;
}
export const CheckIcon: React.FC<IconProps> = ({ className, ...rest }) => (
<motion.svg
fill="none"
stroke="#f5f5f5"
viewBox="0 0 24 24"
height="20px"
width="20px"
className={`stroke-[#00ff59] ${className}`}
{...rest}
{...popAnimation}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M5 13l4 4L19 7"
/>
</motion.svg>
);
export const CopyIcon: React.FC<IconProps> = ({ className, ...rest }) => (
<svg
fill="none"
stroke="#f5f5f5"
viewBox="0 0 24 24"
height="20px"
width="20px"
xmlns="http://www.w3.org/2000/svg"
className={`stroke-black/45 ${className}`}
{...rest}
>
<path
className="stroke-foreground"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.6"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
/>
</svg>
);
Here’s where it all comes together. This component handles the file name, icons, copy functionality, and of course, the code itself.
"use client";
import Image from "next/image";
import React, { useState, useId } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { CheckIcon, CopyIcon } from "@/components/Icons";
interface FileNameProps extends React.HTMLAttributes<HTMLDivElement> {
icon: string;
fileName: string;
}
const FileName: React.FC<FileNameProps> = ({ icon, fileName, children, ...props }) => {
const [isCopied, setIsCopied] = useState(false);
const [isPopping, setIsPopping] = useState(false);
const uniqueId = useId();
const handleCopy = () => {
const range = document.createRange();
const element = document.getElementById(uniqueId);
if (element) {
range.selectNodeContents(element);
const selection = window.getSelection();
if (selection) {
selection.removeAllRanges();
selection.addRange(range);
document.execCommand("copy");
selection.removeAllRanges();
}
}
setIsCopied(true);
setIsPopping(true);
setTimeout(() => {
setIsCopied(false);
setIsPopping(false);
}, 2000);
};
return (
<div className="my-4 relative">
<div className="relative flex flex-col items-start space-x-2" {...props}>
{/* code block details */}
<div className="w-full flex bg-background-tertiary rounded-t-md overflow-hidden">
<div className="bg-background-tertiary flex items-center justify-between w-full py-2.5 h-8">
<div className="flex items-center px-3 gap-2">
<span className="file-icon">
<Image
src={`../packages/svg/icons/${icon}.svg`}
alt={icon}
width={18}
height={18}
className="!m-0"
/>
</span>
<b className="text-xs font-thin font-mono">{fileName}</b>
</div>
</div>
</div>
{/* copy button */}
<div className="sticky top-[5.4rem] whitespace-nowrap z-10 w-full" onClick={handleCopy}>
<div className="absolute bottom-4 right-4 flex items-center cursor-pointer gap-1 h-full bg-background-tertiary">
<div className="py-1 pl-1.5 rounded bg-background-tertiary">
{isCopied ? (
<motion.div
initial={{ scale: 0.6 }}
animate={{ scale: 1 }}
exit={{ scale: 0.6 }}
transition={{ type: "spring", stiffness: 500, damping: 30 }}
className="flex items-center gap-1"
>
<CheckIcon className="text-primary" />
<span className="text-xs mr-2">Copied!</span>
</motion.div>
) : (
<div className="flex items-center gap-1">
<CopyIcon className="text-foreground" />
<span className="text-xs mr-2">Copy code</span>
</div>
)}
</div>
</div>
</div>
{/* code block content */}
<div className="w-full py-0 codeblock-container relative" id={uniqueId}>
{children}
</div>
</div>
</div>
);
};
export default FileName;
<FileName>
<FileName icon="py" fileName="main.py">
```py
print("Inside Custom Code Block!")
```
</FileName>
<FileName icon="mdx" fileName="blog-url.mdx">
print("Inside Custom Code Block!")
Styles come and go. Good design is a language, not a style.
By now, you’ve built a custom code block that’s stylish, functional, and unapologetically yours. Not only does it fit your application like a glove, but it also comes with bragging rights: “Yes, I wrote this from scratch.” And isn’t that what being a developer is all about?
Configuring PowerShell (shell prompt)
Markdown Syntax Guide
Custom Content Code Block Using HTML CSS JS