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.

Installation

Install the sugar-high package.

pnpm add sugar-high

Customize 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.

API Reference

PropTypeDefault
childrenstring
titlestring
collapsiblebooleantrue

Title

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>

Basic Usage

import { CodeBlock } from "@/ui";

<CodeBlock>{`const x = 1;`}</CodeBlock>

Source

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>
  );
}