---
title: Code Block
description: Displays code, terminal output, logs, and diffs, with optional syntax highlighting.
icon: code
author:
  name: shadcn
  url: https://github.com/shadcn
sidebar:
  badge: Updated
search:
  keywords: [syntax highlighting, code snippet, terminal, diff, shiki]
related:
  - /code-tabs
  - /code-editor
  - /use-highlighted-code
mode: wide
---

<ComponentPreview name="code-block-demo" />

<PoweredBy
  packages={[
    { name: 'Shadcn UI', url: 'https://ui.shadcn.com/docs/components/base/code-block' },
    { name: 'Shiki', url: 'https://shiki.style/' },
  ]}
/>

## Installation

```package-install
npx shadcn@latest add @redpanda/code-block
```

The install brings the [`useHighlightedCode`](/docs/use-highlighted-code) hook and the `highlighter` library with it. Highlighting is optional: a block renders any string you give it.

## Usage

```tsx
import {
  CodeBlock,
  CodeBlockActions,
  CodeBlockContent,
  CodeBlockCopyButton,
  CodeBlockHeader,
  CodeBlockTitle,
} from '@/components/redpanda-ui/components/code-block';
```

```tsx
<CodeBlock>
  <CodeBlockHeader>
    <CodeBlockTitle>Terminal</CodeBlockTitle>
    <CodeBlockActions>
      <CodeBlockCopyButton value="rpk topic list" />
    </CodeBlockActions>
  </CodeBlockHeader>
  <CodeBlockContent>rpk topic list</CodeBlockContent>
</CodeBlock>
```

For a block built from one string, use [`SimpleCodeBlock`](#simple-code-block).

## Anatomy

```text
CodeBlock
├── CodeBlockHeader
│   ├── CodeBlockTitle
│   └── CodeBlockActions
│       ├── CodeBlockWrapButton
│       └── CodeBlockCopyButton
├── CodeBlockContent
│   └── CodeBlockLines (or CodeBlockLine)
└── CodeBlockFooter
```

Without a header, `CodeBlockActions` floats over the top end corner of the block.
Its default `visibility="hover"` reveals controls on pointer hover, keyboard focus,
or coarse-pointer devices. Use `visibility="always"` for persistent floating controls;
`SimpleCodeBlock` selects this recipe when it has no title. Copy errors remain visible
until the next attempt in either mode.

## When to use

- **Use CodeBlock** for code, commands, configuration, logs, stack traces, and diffs that people read and copy.
- **Use [Code Tabs](/docs/code-tabs)** for the same snippet in several package managers or languages.
- **Use [Code Editor](/docs/code-editor)** when people edit the code.
- **Use an inline `<code>`** for a name or a short command inside a sentence.

## Syntax highlighting

`CodeBlockLines` turns a string into `CodeBlockLine`s, highlighted when you give it a `language`:

```tsx
<CodeBlock>
  <CodeBlockContent>
    <CodeBlockLines code={code} language="ts" />
  </CodeBlockContent>
</CodeBlock>
```

It keys each line by where it starts in the code, so a finished line keeps its node while code streams in, and no call site needs an index key. To render the lines yourself, call [`useHighlightedCode(code, language)`](/docs/use-highlighted-code), which returns one React node per line, and put each one in a `CodeBlockLine`.

- **Languages load on first use.** The list lives in `lib/highlighter`: `bash`, `css`, `diff`, `go`, `html`, `java`, `javascript`, `json`, `jsx`, `log`, `markdown`, `powershell`, `protobuf`, `python`, `rust`, `sql`, `toml`, `tsx`, `typescript`, and `yaml`, plus aliases such as `ts`, `js`, `sh`, `yml`, `rs`, and `proto`. A `language-*` class from a Markdown fence works too. Add a language by adding one line to that list.
- **An unlisted language renders as plain text**, with no highlighter loaded for it.
- **`"ansi"` renders terminal colours** from ANSI escape codes. Colours do not carry across lines.
- **Streaming is cheap.** As code arrives, only the new lines are tokenized, and finished lines keep their nodes.
- **On the server**, `await highlightCode(code, language)` from `@/lib/highlighter` returns the same lines and keeps Shiki out of the client bundle.

Highlighting runs Shiki on its JavaScript regex engine, so no WASM loads. Shiki's core, engine, themes, and grammars load with the first block that highlights. Tokens use `github-light-high-contrast` and `github-dark-default`; the theme's `code-token` utility selects the palette for the surrounding surface, including terminal blocks.

## Examples

### Simple code block

`SimpleCodeBlock` builds the whole block from one string: highlighting, a header when there is a `title`, and wrap and copy buttons. Without a title, the buttons float over the block and stay visible, as the copy button did before the parts. `wrap` sets whether lines start wrapped, and `allowWrap={false}` hides the wrap toggle.

<ComponentPreview name="code-block-simple-demo" />

```tsx
<SimpleCodeBlock code={query} language="sql" lineNumbers title="top-customers.sql" />
```

### Floating actions

Without a header, the actions appear on hover or focus, and stay shown on touch screens.

<ComponentPreview name="code-block-no-title-demo" />

### Plain

<ComponentPreview name="code-block-no-copy-button-demo" />

### Line numbers

`lineNumbers` numbers each `CodeBlockLine`, and `startLine` sets the first number. Numbers are a CSS counter, so they are never part of copied or selected text, and screen readers skip them. Put a string's lines in with `CodeBlockLines`: unlike Shadcn UI's block, `CodeBlockContent` does not split a string child itself.

<ComponentPreview name="code-block-line-numbers-demo" />

### Diff and highlighted lines

Set `variant` on a line to `added`, `removed`, or `highlighted`, or give `CodeBlockLines` a `variants` map by line number. Added and removed lines carry a `+` or `−` marker, so the change never depends on colour alone.

```tsx
<CodeBlockLines code={code} language="ts" variants={{ 2: 'removed', 3: 'added' }} />
```

<ComponentPreview name="code-block-diff-demo" />

### Wrap

`wrap` wraps long lines instead of scrolling them. `CodeBlockWrapButton` is the toggle for it, a `Button` with `aria-pressed`. It changes the display only: copying keeps the source's own line breaks.

<ComponentPreview name="code-block-wrap-demo" />

### Show more

Cap the content with a `max-h-*` class, and toggle it from a `Button` in `CodeBlockFooter`. Give the button `aria-expanded`, and point its `aria-controls` at the content's `id`.

<ComponentPreview name="code-block-show-more-demo" />

### Terminal

`variant="terminal"` keeps the block dark in both app themes. It sets `data-theme="dark"` on the block, so its tokens and highlighting both resolve to the dark theme. Pair it with `size="sm"` and the `"ansi"` language for command output.

<ComponentPreview name="code-block-terminal-demo" />

### Destructive

`variant="destructive"` is for errors and failed commands.

<ComponentPreview name="code-block-destructive-demo" />

## Redpanda examples

### Client languages

The same order produced to the `orders` topic from Go (franz-go), Python (confluent-kafka), Java, TypeScript (KafkaJS), and Rust (rdkafka). For the same step in several languages, put one block per language in a [`TabsContent`](/docs/tabs).

<ComponentPreview name="code-block-languages-demo" />

### Schema Registry

A topic's value schema in Protobuf and Avro, with line numbers, and the `rpk registry` commands that register it in a terminal block.

<ComponentPreview name="code-block-schema-demo" />

### Redpanda Connect

A pipeline that reads `orders`, enriches each record with a Bloblang mapping, and writes `orders-enriched`. The mapping lines are highlighted through `CodeBlockLines` `variants`.

<ComponentPreview name="code-block-connect-demo" />

### rpk

A session that points `rpk` at a local cluster, then creates, produces to, and consumes from a topic.

<ComponentPreview name="code-block-rpk-demo" />

## Accessibility

- `CodeBlockContent` renders a `pre` with a `code` element, always left to right, while the header follows the page direction.
- The content takes focus while it scrolls, so keyboard users can scroll a long line or a capped block. A block that fits is not a tab stop, so a page of short commands is not a page of tab stops. Pass `tabIndex` to decide yourself.
- Floating actions show on focus as well as hover.
- `CodeBlockCopyButton` keeps the name "Copy", and a status region announces "Copied". The result is announced once, not again by a renamed button.
- While the write is pending, the button stays focused but ignores clicks. When the clipboard refuses the write, an alert reads "Could not copy code. Try again." and stays, on a row of its own that fits a narrow block, until the next try succeeds.
- Line numbers are hidden from screen readers, and diff markers back up their colour.

## API reference

### CodeBlock

Also takes every `div` prop.

| Prop | Type | Default | Description |
| - | - | - | - |
| `variant?` | `"default" \| "destructive" \| "terminal"` | `"default"` | The tone of the block. `terminal` stays dark in both app themes. |
| `size?` | `"default" \| "sm"` | `"default"` | Tighter spacing and smaller text, for tool and log output. |
| `testId?` | `string` | - | `data-testid` for the block. |

### CodeBlockCopyButton

Also takes every [`Button`](/docs/button) prop. It is a ghost `icon-xs` button by default.

| Prop | Type | Default | Description |
| - | - | - | - |
| `value` | `string \| (() => string)` | - | The text to copy, or a function that returns it at the moment of the click. |
| `onCopied?` | `() => void` | - | Called once the text is on the clipboard. |
| `onClick?` | `(event) => void` | - | Runs before the copy. Call `event.preventDefault()` to skip it. |
| `copyLabel?` | `string` | `"Copy"` | The button's name, which stays put through copying. |
| `copiedLabel?` | `string` | `"Copied"` | Announced once the text is on the clipboard. |
| `copyFailedLabel?` | `string` | `"Could not copy code. Try again."` | Shown until the next try, when the clipboard refuses the write. |

### CodeBlockWrapButton

Also takes every [`Button`](/docs/button) prop. It is a ghost `icon-xs` button by default.

| Prop | Type | Default | Description |
| - | - | - | - |
| `wrapped` | `boolean` | - | Whether the content wraps now. Pass it to `CodeBlockContent` as `wrap`. |
| `onWrappedChange` | `(wrapped: boolean) => void` | - | Called with the next wrap state on click. |
| `label?` | `string` | `"Wrap lines"` | The button's name. |

### CodeBlockContent

Also takes every `pre` prop. Cap its height with a `max-h-*` class.

| Prop | Type | Default | Description |
| - | - | - | - |
| `children?` | `React.ReactNode` | - | A string, `CodeBlockLines`, or `CodeBlockLine` elements. |
| `wrap?` | `boolean` | `false` | Wrap long lines instead of scrolling them. |
| `lineNumbers?` | `boolean` | `false` | Number each `CodeBlockLine`. |
| `startLine?` | `number` | `1` | The number of the first line, with `lineNumbers`. |
| `tabIndex?` | `number` | `0 while it scrolls` | Overrides the default, which is focusable only while the content scrolls. |

### CodeBlockLines

| Prop | Type | Default | Description |
| - | - | - | - |
| `code` | `string` | - | The code, split into one `CodeBlockLine` per line. A trailing newline is dropped. |
| `language?` | `string` | - | Highlights the lines. Without one, or for an unlisted language, they stay plain. |
| `variants?` | `Partial<Record<number, "default" \| "added" \| "removed" \| "highlighted">>` | - | A line's variant, by its line in `code` counting from 1, whatever the content's `startLine`. |

### CodeBlockLine

Also takes every `span` prop. An empty line keeps a newline, so a copied selection keeps blank lines.

| Prop | Type | Default | Description |
| - | - | - | - |
| `variant?` | `"default" \| "added" \| "removed" \| "highlighted"` | `"default"` | Marks the line in a diff, or draws attention to it. |

`CodeBlockHeader`, `CodeBlockTitle`, `CodeBlockActions`, and `CodeBlockFooter` take every `div` prop. `CodeBlockTitle` truncates, and sizes an icon placed before the text.
`CodeBlockActions` also accepts `visibility="hover" | "always"` (default: `"hover"`).

### SimpleCodeBlock

| Prop | Type | Default | Description |
| - | - | - | - |
| `code` | `string` | - | The code to show and copy. |
| `language?` | `string` | - | Highlights the code. An unlisted language renders as plain text. |
| `title?` | `React.ReactNode` | - | Shows a header with this title. |
| `icon?` | `React.ReactNode` | - | Shown before the title. |
| `allowCopy?` | `boolean` | `true` | Show the copy button. |
| `allowWrap?` | `boolean` | `true` | Show the wrap toggle. Copying keeps the source either way. |
| `onCopy?` | `() => void` | - | Called once the code is on the clipboard. |
| `lineNumbers?` | `boolean` | `false` | Number each line. |
| `wrap?` | `boolean` | `false` | Whether long lines start wrapped. The wrap toggle changes it from there. |
| `variant?` | `"default" \| "destructive" \| "terminal"` | `"default"` | The tone of the block. |
| `size?` | `"default" \| "sm"` | `"default"` | Use default or compact spacing. |
| `width?` | `"auto" \| "sm" \| "md" \| "lg" \| "full"` | `"md"` | Caps the width of the block. |
| `maxHeight?` | `"sm" \| "md" \| "lg" \| "none"` | `"md"` | Caps the content at 300, 600, or 800 pixels, after which it scrolls. |
| `testId?` | `string` | - | `data-testid` for the block. |
| `copyTestId?` | `string` | - | `data-testid` for the copy button. Defaults to `${testId}-copy` when `testId` is set. |

## Migrating from the previous CodeBlock

`CodeBlock` is now a set of parts, matching Shadcn UI's code block. `SimpleCodeBlock` keeps its props, so most call sites need no change. They gain syntax highlighting and a wrap toggle, which `allowWrap={false}` hides.

| Before                                      | After                                                                                                                          |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `<CodeBlock title="x" icon={i}>`            | `<CodeBlockHeader><CodeBlockTitle>{i}x</CodeBlockTitle></CodeBlockHeader>`                                                     |
| `allowCopy` (on by default)                 | Add `<CodeBlockCopyButton value={code} />` inside `CodeBlockActions`. Leave it out for no button.                              |
| `onCopy`                                    | `onCopied` on `CodeBlockCopyButton`                                                                                            |
| `copyTestId`                                | `testId` on `CodeBlockCopyButton`                                                                                              |
| `<Pre><code>{code}</code></Pre>`            | `<CodeBlockContent>{code}</CodeBlockContent>`, or `<CodeBlockLines code={code} />` inside it for line numbers and highlighting |
| `size="md"` or `size="lg"`                  | Leave `size` out. `size="sm"` stays.                                                                                           |
| `width="full"`                              | Leave it out: the block fills its container. Cap it with a `max-w-*` class.                                                    |
| `maxHeight="sm"`                            | A `max-h-*` class on `CodeBlockContent`                                                                                        |
| `viewportProps`                             | Props on `CodeBlockContent`                                                                                                    |
| A string `icon` holding HTML                | An icon element                                                                                                                |
| The block's outer margin (`my-3` to `my-8`) | None: space the block from its container.                                                                                      |

## Migrating from Code Block (Dynamic)

[Code Block (Dynamic)](/docs/code-block-dynamic) was removed in this breaking release. Use this component instead.

| Before                                                          | After                                                                        |
| --------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `<DynamicCodeBlock code={code} lang="tsx" />`                   | `<SimpleCodeBlock code={code} language="tsx" />`                             |
| `<SyncCodeBlock code={code} lang="bash" />`                     | `<SimpleCodeBlock code={code} language="bash" />`                            |
| `themes={TERMINAL_THEMES}` with `keepBackground`                | `variant="terminal"`                                                         |
| `title`, `icon`, `allowCopy`, `testId`, `copyTestId`            | The same props on `SimpleCodeBlock`                                          |
| `onCopied`                                                      | `onCopy` on `SimpleCodeBlock`, or `onCopied` on `CodeBlockCopyButton`        |
| `options` with custom Shiki themes, transformers, or components | Compose the parts, and render your own lines in `CodeBlockLine`              |
| `data-line-numbers` and `data-line-numbers-start`               | `lineNumbers` and `startLine` on `CodeBlockContent`, around `CodeBlockLines` |

## Credits

- Based on the [Shadcn UI Code Block](https://ui.shadcn.com/docs/components/base/code-block) component and its `useHighlightedCode` hook.
