Skip to content
Corsair UI
Browse the docs

ComponentsNo. 14 of 109

Code Block

A block of code with an optional caption, a language label and a copy button.

Installation

pnpm dlx shadcn@latest add @corsair-ui/code-block

No setup needed: the CLI finds @corsair-ui in the shadcn registry directory. The files land in your project as source: edit them like your own code. More in Installation.

Also adds
utilsbuttoncopy-button
npm
@radix-ui/react-slotclass-variance-authorityclsxlucide-reacttailwind-merge

Examples

Title, language and copy

Install
bash
pnpm dlx shadcn@latest add @corsair-ui/theme @corsair-ui/dialog
welcome.tsx
tsx
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog";

export function Welcome() {
  return (
    <Dialog>
      <DialogTrigger>Open</DialogTrigger>
      <DialogContent>
        <DialogTitle>Welcome aboard</DialogTitle>
      </DialogContent>
    </Dialog>
  );
}

API

PropTypeDefault
code

What the copy button copies.

string—
language / title

A small label and a caption.

string / ReactNode—
showCopyButton / copyLabel

The copy button and its name.

boolean / stringtrue / "Copy code"
wrap

Wrap long lines instead of scrolling sideways.

booleanfalse
children

Highlighted markup of your own to show instead of the raw code.

ReactNode—

Accessibility

  • A figure named by its title. The scrolling <pre> takes focus so it scrolls from the keyboard.
  • The copy button announces when it copied.

Compatibility

Tailwind CSS
3.4 and 4, both checked in CI
React
19
Rendering
Works in server components

Source

1 file, installed as source you own
code-block.tsxView on GitHub
import { useId, type ComponentProps, type ReactNode } from "react";
import { cn } from "@/registry/default/lib/utils";
import { CopyButton } from "@/registry/default/ui/copy-button";

interface CodeBlockProps extends Omit<ComponentProps<"figure">, "children" | "title"> {
  /** The source. Rendered as is unless `children` is given, and always what the copy button copies. */
  code: string;
  /** Shown as a small label in the corner and exposed as `data-language`. */
  language?: string;
  /** A caption above the code, such as a file name. */
  title?: ReactNode;
  showCopyButton?: boolean;
  /** Accessible name of the copy button. */
  copyLabel?: string;
  /** Wrap long lines instead of scrolling sideways. */
  wrap?: boolean;
  /**
   * Highlighted content to render inside `<code>` in place of the raw
   * `code`, such as the token spans from a syntax highlighter.
   */
  children?: ReactNode;
  /** Extra classes for the `<pre>`. */
  preClassName?: string;
}

/**
 * A block of code with an optional caption, a language label and a copy
 * button. The code sits in a `<pre>` that takes keyboard focus, so a long
 * line can be scrolled with the arrow keys; the caption names the figure
 * for screen readers. The copy button announces
 * "Copied" once the text is on the clipboard. It animates nothing.
 *
 * @example
 * <CodeBlock title="Install" language="bash" code="pnpm dlx shadcn@latest add @corsair-ui/code-block" />
 */
function CodeBlock({
  code,
  language,
  title,
  showCopyButton = true,
  copyLabel = "Copy code",
  wrap = false,
  children,
  className,
  preClassName,
  ...props
}: CodeBlockProps) {
  const toolbar = showCopyButton || Boolean(language);
  const titleId = useId();

  return (
    <figure
      data-slot="code-block"
      data-language={language}
      data-wrap={wrap}
      aria-labelledby={title ? titleId : undefined}
      className={cn(
        "bg-muted/50 text-foreground relative overflow-hidden rounded-lg border text-sm",
        className
      )}
      {...props}
    >
      {title ? (
        <figcaption
          id={titleId}
          data-slot="code-block-title"
          className={cn(
            "text-muted-foreground border-b px-4 py-2.5 text-xs font-medium",
            toolbar && "pr-24"
          )}
        >
          {title}
        </figcaption>
      ) : null}
      {toolbar ? (
        <div
          data-slot="code-block-toolbar"
          className="absolute top-1 right-1 flex items-center gap-1"
        >
          {language ? (
            <span
              data-slot="code-block-language"
              className="text-muted-foreground px-1.5 font-mono text-xs select-none"
            >
              {language}
            </span>
          ) : null}
          {showCopyButton ? <CopyButton size="sm" value={code} label={copyLabel} /> : null}
        </div>
      ) : null}
      <pre
        data-slot="code-block-pre"
        // A scrolling region has to take focus so keyboard users can scroll
        // it (WCAG 2.1.1, axe "scrollable-region-focusable"); a `<pre>` has no
        // interactive role to give it, so the rule does not apply here.
        // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
        tabIndex={0}
        className={cn(
          "overflow-x-auto p-4 font-mono text-sm leading-relaxed outline-none",
          "focus-visible:ring-ring/50 focus-visible:ring-[3px] focus-visible:ring-inset",
          wrap ? "break-words whitespace-pre-wrap" : "whitespace-pre",
          toolbar && !title && "pr-24",
          preClassName
        )}
      >
        <code data-slot="code-block-code">{children ?? code}</code>
      </pre>
    </figure>
  );
}

export { CodeBlock, type CodeBlockProps };