Code Block

Syntax-highlighted code display with copy support.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/code-block.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

Code

"use client"; import { type ComponentType, type ReactNode, useEffect, useRef, useState, } from "react"; import { Check, Copy } from "lucide-react"; import { useTheme } from "next-themes"; import type { SyntaxHighlighterProps } from "react-syntax-highlighter"; import { cn } from "../../lib/utils"; import { Button } from "../button/button"; import { useCopyToClipboard } from "../copy-button/copy-button"; type PrismStyle = SyntaxHighlighterProps["style"]; type LoadedHighlighter = { oneDark: PrismStyle; oneLight: PrismStyle; SyntaxHighlighter: ComponentType<SyntaxHighlighterProps>; }; type CodeBlockProps = { children: ReactNode; className?: string; language?: string; showLanguage?: boolean; }; function extractTextFromChildren(node: ReactNode): string { if (typeof node === "string") { return node; } if (typeof node === "number") { return String(node); } if (Array.isArray(node)) { return node.map(extractTextFromChildren).join(""); } if ( node && typeof node === "object" && "props" in node && node.props && typeof node.props === "object" && "children" in node.props ) { return extractTextFromChildren(node.props.children as ReactNode); } return String(node ?? ""); } function findScrollableParent( element: HTMLElement | null, ): HTMLElement | undefined { if (!element) return undefined; if (element.scrollHeight > element.clientHeight) return element; return findScrollableParent(element.parentElement); } export function CodeBlock({ children, className, language = "typescript", showLanguage = false, }: CodeBlockProps) { const { copied, copy } = useCopyToClipboard(); // react-syntax-highlighter (~10MB) is dynamic-imported on mount so the // @vllnt/ui barrel's static graph never reaches it — barrel consumers that // never render a CodeBlock ship zero bytes of it. Null until the chunk loads. const [highlighter, setHighlighter] = useState<LoadedHighlighter | null>( null, ); const { systemTheme, theme } = useTheme(); const resolvedTheme = theme === "system" ? systemTheme : theme; const isDark = resolvedTheme !== "light"; const code = extractTextFromChildren(children); const scrollRef = useRef<HTMLDivElement>(null); useEffect(() => { let active = true; void Promise.all([ import("react-syntax-highlighter"), import("react-syntax-highlighter/dist/esm/styles/prism"), ]).then(([module_, styles]) => { if (!active) return; setHighlighter({ oneDark: styles.oneDark, oneLight: styles.oneLight, SyntaxHighlighter: module_.Prism, }); }); return () => { active = false; }; }, []); useEffect(() => { const element = scrollRef.current; if (!element) return; const onWheel = (event: WheelEvent) => { if (Math.abs(event.deltaY) <= Math.abs(event.deltaX)) return; const scrollable = findScrollableParent(element); if (scrollable) { scrollable.scrollTop += event.deltaY; event.preventDefault(); } }; element.addEventListener("wheel", onWheel, { passive: false }); return () => { element.removeEventListener("wheel", onWheel); }; }, []); const handleCopy = () => { void copy(code); }; const SyntaxHighlighter = highlighter?.SyntaxHighlighter; const codeStyle = isDark ? highlighter?.oneDark : highlighter?.oneLight; return ( <div className={cn( "relative w-full overflow-hidden rounded-md border bg-background", className, )} > <div className="relative overflow-x-auto overflow-y-hidden touch-pan-y" ref={scrollRef} > {SyntaxHighlighter ? ( <SyntaxHighlighter codeTagProps={{ className: "font-mono text-sm", style: { background: "transparent", display: "block", }, }} customStyle={{ background: "oklch(var(--background))", fontSize: "0.875rem", margin: 0, minWidth: "fit-content", overflowY: "hidden", padding: "1rem", }} language={language} style={codeStyle} > {code} </SyntaxHighlighter> ) : ( <pre className="m-0 min-w-fit overflow-y-hidden p-4 font-mono text-sm"> <code className="block bg-transparent">{code}</code> </pre> )} <div className="absolute right-2 top-2 flex items-center gap-2"> {showLanguage ? ( <span className="text-xs font-mono text-muted-foreground uppercase tracking-wider"> {language} </span> ) : null} <Button aria-label={copied ? "Copied" : "Copy code"} className="size-8" onClick={handleCopy} size="icon" variant="ghost" > {copied ? ( <Check className="size-3" /> ) : ( <Copy className="size-3" /> )} </Button> </div> </div> </div> ); }

Dependencies

  • @vllnt/ui@^0.3.0