Introducing Aceternity UI Pro - Premium component packs and templates to build awesome websites.
Logo

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

PropTypeRequiredDescription
languagestringYesThe programming language for syntax highlighting
filenamestringYesThe name of the file to display
highlightLinesnumber[]NoArray of line numbers to highlight. Defaults to []
codestringConditionalThe code content to display
tabsArray<TabConfig>ConditionalArray of tab configurations

Note: Either code OR tabs must be provided, but not both.

TabConfig Object Structure

PropertyTypeRequiredDescription
namestringYesThe name of the tab
codestringYesThe code content for this tab
languagestringNoOverride the default language for this tab
highlightLinesnumber[]NoOverride 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
A product by Aceternity
Building in public at @mannupaaji