CodeBlock
Syntax-highlighted code block with copy button and collapsible overflow.
Requires sugar-high
This component depends on the sugar-high package for syntax highlighting. Install it before using CodeBlock.
Install the sugar-high package.
pnpm add sugar-highCustomize Colors
sugar-high reads from CSS variables prefixed with --sh-*. Add these to your global.css to match your theme — swap any values to customize.
src/app/global.css
/* Syntax highlighting (sugar-high) — light theme */
:root {
--sh-class: oklch(0.45 0.08 250);
--sh-identifier: oklch(0.35 0.02 60);
--sh-sign: oklch(0.55 0.015 75);
--sh-property: oklch(0.4 0.1 250);
--sh-entity: oklch(0.45 0.1 180);
--sh-jsxliterals: oklch(0.48 0.12 290);
--sh-string: oklch(0.42 0.1 160);
--sh-keyword: oklch(0.48 0.15 25);
--sh-comment: oklch(0.58 0.01 75);
}
/* Syntax highlighting — dark theme */
.dark {
--sh-class: oklch(0.72 0.1 250);
--sh-identifier: oklch(0.82 0.02 80);
--sh-sign: oklch(0.62 0.012 72);
--sh-property: oklch(0.72 0.12 250);
--sh-entity: oklch(0.72 0.1 180);
--sh-jsxliterals: oklch(0.72 0.12 290);
--sh-string: oklch(0.72 0.1 160);
--sh-keyword: oklch(0.72 0.14 25);
--sh-comment: oklch(0.68 0.012 72);
}Using this starter's global.css? These tokens are already included.
| Prop | Type | Default |
|---|---|---|
| children | string | — |
| title | string | — |
| collapsible | boolean | true |
Button.tsx
export function Button({ children }) {
return <button>{children}</button>;
}<CodeBlock title="Button.tsx">{`export function Button({ children }) {
return <button>{children}</button>;
}`}</CodeBlock>Collapsible
Blocks longer than 15 lines automatically collapse with a "Show code" toggle. Set collapsible={false} to disable.
<CodeBlock collapsible={false}>
{longCodeString}
</CodeBlock>import { CodeBlock } from "@/ui";
<CodeBlock>{`const x = 1;`}</CodeBlock>src/ui/CodeBlock.tsx
"use client";
import { useState, useCallback, useMemo } from "react";
import { highlight } from "sugar-high";
import { cn } from "@/utils";
import { CheckIcon, ContentCopyIcon } from "./icons";
interface CodeBlockProps {
children: string;
className?: string;
title?: string;
collapsible?: boolean;
}
const COLLAPSE_THRESHOLD_LINES = 15;
export function CodeBlock({
children,
className,
title,
collapsible = true,
}: CodeBlockProps) {
const [copied, setCopied] = useState(false);
const [expanded, setExpanded] = useState(false);
const handleCopy = useCallback(() => {
navigator.clipboard.writeText(children);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}, [children]);
const highlighted = useMemo(() => highlight(children), [children]);
const lineCount = useMemo(() => children.split("\n").length, [children]);
const isCollapsible = collapsible && lineCount > COLLAPSE_THRESHOLD_LINES;
const isCollapsed = isCollapsible && !expanded;
return (
<div
className={cn(
"overflow-hidden rounded-lg border border-divider bg-root-100",
className,
)}
>
<div
className={cn(
"flex items-center border-b border-divider px-4 py-2",
title ? "justify-between" : "justify-end",
)}
>
{title && (
<span className="text-xs font-medium text-root-contrast-subtle">
{title}
</span>
)}
<button
type="button"
onClick={handleCopy}
className="inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium text-root-contrast-subtle transition-colors hover:bg-divider hover:text-root-contrast"
aria-label={copied ? "Copied" : "Copy code"}
>
{copied ? "Copied" : "Copy"}
{copied ? (
<CheckIcon className="size-3.5" />
) : (
<ContentCopyIcon className="size-3.5" />
)}
</button>
</div>
<div
className={cn("relative overflow-hidden", isCollapsed && "max-h-80")}
>
<pre className="overflow-x-auto p-5 text-sm leading-relaxed text-root-contrast/80">
<code dangerouslySetInnerHTML={{ __html: highlighted }} />
</pre>
{isCollapsed && (
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-20 bg-linear-to-t from-root-100 to-transparent" />
)}
</div>
{isCollapsible && (
<button
type="button"
onClick={() => setExpanded((prev) => !prev)}
className="w-full border-t border-divider px-4 py-2 text-xs font-medium text-root-contrast/60 transition-colors hover:bg-root-contrast/5 hover:text-root-contrast"
>
{expanded ? "Hide code" : "Show code"}
</button>
)}
</div>
);
}