Code Block
A configurable code block component built on top of react-syntax-highlighter.
DummyComponent.jsx
1const DummyComponent = () => {
2 const [count, setCount] = React.useState(0);
3
4 const handleClick = () => {
5 setCount(prev => prev + 1);
6 };
7
8 return (
9 <div className="p-4 border rounded-lg">
10 <h2 className="text-xl font-bold mb-4">Fights Counter</h2>
11 <p className="mb-2">Fight Club Fights Count: {count}</p>
12 <button
13 onClick={handleClick}
14 className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
15 >
16 Increment
17 </button>
18 </div>
19 );
20};
21
Installation
Install dependencies
npm i motion clsx tailwind-merge
Install code editor dependencies
npm i react-syntax-highlighter @types/react-syntax-highlighter @tabler/icons-react
For React 19 / Next.js 15 users, follow the following packages
For React 19 / Next.js 15 users, either use the --legacy-peer-deps
flag or add the following overrides in your package.json
file:
"overrides": {
"react-syntax-highlighter": "15.0.0"
}
Add util file
lib/utils.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Copy the source code
components/ui/code-block.tsx
"use client";
import React from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { atomDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import { IconCheck, IconCopy } from "@tabler/icons-react";
type CodeBlockProps = {
language: string;
filename: string;
highlightLines?: number[];
} & (
| {
code: string;
tabs?: never;
}
| {
code?: never;
tabs: Array<{
name: string;
code: string;
language?: string;
highlightLines?: number[];
}>;
}
);
export const CodeBlock = ({
language,
filename,
code,
highlightLines = [],
tabs = [],
}: CodeBlockProps) => {
const [copied, setCopied] = React.useState(false);
const [activeTab, setActiveTab] = React.useState(0);
const tabsExist = tabs.length > 0;
const copyToClipboard = async () => {
const textToCopy = tabsExist ? tabs[activeTab].code : code;
if (textToCopy) {
await navigator.clipboard.writeText(textToCopy);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
};
const activeCode = tabsExist ? tabs[activeTab].code : code;
const activeLanguage = tabsExist
? tabs[activeTab].language || language
: language;
const activeHighlightLines = tabsExist
? tabs[activeTab].highlightLines || []
: highlightLines;
return (
<div className="relative w-full rounded-lg bg-slate-900 p-4 font-mono text-sm">
<div className="flex flex-col gap-2">
{tabsExist && (
<div className="flex overflow-x-auto">
{tabs.map((tab, index) => (
<button
key={index}
onClick={() => setActiveTab(index)}
className={`px-3 !py-2 text-xs transition-colors font-sans ${
activeTab === index
? "text-white"
: "text-zinc-400 hover:text-zinc-200"
}`}
>
{tab.name}
</button>
))}
</div>
)}
{!tabsExist && filename && (
<div className="flex justify-between items-center py-2">
<div className="text-xs text-zinc-400">{filename}</div>
<button
onClick={copyToClipboard}
className="flex items-center gap-1 text-xs text-zinc-400 hover:text-zinc-200 transition-colors font-sans"
>
{copied ? <IconCheck size={14} /> : <IconCopy size={14} />}
</button>
</div>
)}
</div>
<SyntaxHighlighter
language={activeLanguage}
style={atomDark}
customStyle={{
margin: 0,
padding: 0,
background: "transparent",
fontSize: "0.875rem", // text-sm equivalent
}}
wrapLines={true}
showLineNumbers={true}
lineProps={(lineNumber) => ({
style: {
backgroundColor: activeHighlightLines.includes(lineNumber)
? "rgba(255,255,255,0.1)"
: "transparent",
display: "block",
width: "100%",
},
})}
PreTag="div"
>
{String(activeCode)}
</SyntaxHighlighter>
</div>
);
};
Props
Prop | Type | Required | Description |
---|---|---|---|
language | string | Yes | The programming language for syntax highlighting |
filename | string | Yes | The name of the file to display |
highlightLines | number[] | No | Array of line numbers to highlight. Defaults to [] |
code | string | Conditional | The code content to display |
tabs | Array<TabConfig> | Conditional | Array of tab configurations |
Note: Either code OR tabs must be provided, but not both.
TabConfig Object Structure
Property | Type | Required | Description |
---|---|---|---|
name | string | Yes | The name of the tab |
code | string | Yes | The code content for this tab |
language | string | No | Override the default language for this tab |
highlightLines | number[] | No | Override the default highlighted lines for this tab |
Code Preview With Multiple Tabs
1<div className="p-4 border rounded-lg">
2 <h2 className="text-xl font-bold mb-4">Fights Counter</h2>
3 <p className="mb-2">Fight Club Fights Count: {count}</p>
4 <button
5 onClick={handleClick}
6 className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
7 >
8 Increment
9 </button>
10</div>
11