# Code Block

Multi-line code with a copy button and optional line numbers.

Source: [code-block.tsx](https://github.com/jakejarvis/jarvis-ui/blob/main/src/components/ui/code-block.tsx)

```tsx
import { CodeBlock } from "@/components/ui/code-block";

const code = `pnpm dlx shadcn@latest init https://ui.jarv.is/r/base.json
pnpm dlx shadcn@latest add @jarvis/code-block
pnpm dev`;

export default function CodeBlockBasic() {
  return <CodeBlock language="bash" code={code} className="max-w-lg" />;
}
```

## Installation

Add the registry, once per project:

```bash
npx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json
```

Then add the component:

```bash
npx shadcn@latest add @jarvis/code-block
```

## Usage

```tsx
import { CodeBlock } from "@/components/ui/code-block";

export function InstallCommand() {
  return <CodeBlock language="bash" code="pnpm add @base-ui/react" />;
}
```

Pass `onCopy` to run something after a successful copy, such as showing a toast.

## Examples

### With a label

`label` adds a header, such as a file name. `lineNumbers` numbers each line.

```tsx
import { CodeBlock } from "@/components/ui/code-block";

const code = `import { Button } from "@/components/ui/button";

export function SaveButton({ onSave }: { onSave: () => void }) {
  return (
    <Button variant="secondary" onClick={onSave}>
      Save changes
    </Button>
  );
}
`;

export default function CodeBlockWithLabel() {
  return (
    <CodeBlock
      label="save-button.tsx"
      language="tsx"
      code={code}
      lineNumbers
      className="max-w-lg"
    />
  );
}
```

### Long lines

Long lines scroll sideways rather than wrapping.

```tsx
import { CodeBlock } from "@/components/ui/code-block";

const code = `DATABASE_URL="postgres://app:4f9c2e7d1b8a@db.internal.example.com:5432/production?sslmode=require&application_name=web"
REDIS_URL="redis://cache.internal.example.com:6379/0"
LOG_LEVEL=info`;

export default function CodeBlockLongLines() {
  return <CodeBlock label=".env" language="dotenv" code={code} lineNumbers className="max-w-lg" />;
}
```

### Syntax highlighting

Code Block doesn't highlight code itself. Highlight it with a library such as
[Highlights](https://diffs.com/docs#highlights) or [Shiki](https://shiki.style) and pass one
element per line to `highlightedLines`.

```tsx
import { codeToTokens } from "@pierre/highlights";
import githubLight from "@pierre/highlights/themes/github-light";
import { Fragment } from "react";

import { CodeBlock } from "@/components/ui/code-block";

export function HighlightedCode({ code }: { code: string }) {
  const { tokens } = codeToTokens(code, { lang: "tsx", theme: githubLight });
  const lines = tokens.map((line, index) => (
    <Fragment key={index}>
      {line.map((token, tokenIndex) => (
        <span key={tokenIndex} style={{ color: token.color }}>
          {token.content}
        </span>
      ))}
    </Fragment>
  ));

  return <CodeBlock language="tsx" code={code} highlightedLines={lines} />;
}
```

Highlights runs in the browser too, but it adds its WebAssembly to the bundle, so highlight on the
server or ahead of time where you can.

## Props

`CodeBlock` takes the props of a `<div>`, apart from `children`.

| Part | Extends |
| --- | --- |
| `CodeBlock` | `<div>` |

### CodeBlock

| Prop                | Type                        | Description                                                                                                           |
| ------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `code`              | `string`                    | The code shown and copied.                                                                                            |
| `highlightedLines?` | `React.ReactNode[]`         | Lines to render in place of `code`'s plain text, such as ones highlighted by Shiki. `code` is still what gets copied. |
| `language?`         | `string`                    | The code's language, such as `tsx` or `bash`. Set on the block as `data-language`.                                    |
| `label?`            | `React.ReactNode`           | Optional label, such as a file name, shown in a header bar with the copy button.                                      |
| `lineNumbers?`      | `boolean`                   | Numbers each line in a gutter that stays put while long lines scroll. Default: `false`                                |
| `timeout?`          | `number`                    | How long the copied or failed state lasts, in ms.                                                                     |
| `onCopy?`           | `((value: string) => void)` |                                                                                                                       |
