Code Block
Displays code, terminal output, logs, and diffs, with optional syntax highlighting.
Installation
npx shadcn@latest add @redpanda/code-blockpnpm dlx shadcn@latest add @redpanda/code-blockyarn dlx shadcn@latest add @redpanda/code-blockbunx shadcn@latest add @redpanda/code-blocknubx shadcn@latest add @redpanda/code-blockaube dlx shadcn@latest add @redpanda/code-blockThe install brings the useHighlightedCode hook and the highlighter library with it. Highlighting is optional: a block renders any string you give it.
Usage
import { CodeBlock, CodeBlockActions, CodeBlockContent, CodeBlockCopyButton, CodeBlockHeader, CodeBlockTitle,} from '@/components/redpanda-ui/components/code-block';<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.
Anatomy
CodeBlock├── CodeBlockHeader│ ├── CodeBlockTitle│ └── CodeBlockActions│ ├── CodeBlockWrapButton│ └── CodeBlockCopyButton├── CodeBlockContent│ └── CodeBlockLines (or CodeBlockLine)└── CodeBlockFooterWithout 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 for the same snippet in several package managers or languages.
- Use 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 CodeBlockLines, highlighted when you give it a language:
<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), 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, andyaml, plus aliases such asts,js,sh,yml,rs, andproto. Alanguage-*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/highlighterreturns 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.
<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.
Plain
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.
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.
<CodeBlockLines code={code} language="ts" variants={{ 2: 'removed', 3: 'added' }} />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.
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.
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.
Destructive
variant="destructive" is for errors and failed commands.
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.
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.
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.
rpk
A session that points rpk at a local cluster, then creates, produces to, and consumes from a topic.
Accessibility
CodeBlockContentrenders aprewith acodeelement, 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
tabIndexto decide yourself. - Floating actions show on focus as well as hover.
CodeBlockCopyButtonkeeps 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.
variant?"default" | "destructive" | "terminal"
The tone of the block. `terminal` stays dark in both app themes.
"default" | "destructive" | "terminal""default"size?"default" | "sm"
Tighter spacing and smaller text, for tool and log output.
"default" | "sm""default"testId?string
`data-testid` for the block.
stringCodeBlockCopyButton
Also takes every Button prop. It is a ghost icon-xs button by default.
valuestring | (() => string)
The text to copy, or a function that returns it at the moment of the click.
string | (() => string)onCopied?() => void
Called once the text is on the clipboard.
() => voidonClick?(event) => void
Runs before the copy. Call `event.preventDefault()` to skip it.
(event) => voidcopyLabel?string
The button's name, which stays put through copying.
string"Copy"copiedLabel?string
Announced once the text is on the clipboard.
string"Copied"copyFailedLabel?string
Shown until the next try, when the clipboard refuses the write.
string"Could not copy code. Try again."CodeBlockWrapButton
Also takes every Button prop. It is a ghost icon-xs button by default.
wrappedboolean
Whether the content wraps now. Pass it to `CodeBlockContent` as `wrap`.
booleanonWrappedChange(wrapped: boolean) => void
Called with the next wrap state on click.
(wrapped: boolean) => voidlabel?string
The button's name.
string"Wrap lines"CodeBlockContent
Also takes every pre prop. Cap its height with a max-h-* class.
children?React.ReactNode
A string, `CodeBlockLines`, or `CodeBlockLine` elements.
React.ReactNodewrap?boolean
Wrap long lines instead of scrolling them.
booleanfalselineNumbers?boolean
Number each `CodeBlockLine`.
booleanfalsestartLine?number
The number of the first line, with `lineNumbers`.
number1tabIndex?number
Overrides the default, which is focusable only while the content scrolls.
number0 while it scrollsCodeBlockLines
codestring
The code, split into one `CodeBlockLine` per line. A trailing newline is dropped.
stringlanguage?string
Highlights the lines. Without one, or for an unlisted language, they stay plain.
stringvariants?Partial<Record<number, "default" | "added" | "removed" | "highlighted">>
A line's variant, by its line in `code` counting from 1, whatever the content's `startLine`.
Partial<Record<number, "default" | "added" | "removed" | "highlighted">>CodeBlockLine
Also takes every span prop. An empty line keeps a newline, so a copied selection keeps blank lines.
variant?"default" | "added" | "removed" | "highlighted"
Marks the line in a diff, or draws attention to it.
"default" | "added" | "removed" | "highlighted""default"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
codestring
The code to show and copy.
stringlanguage?string
Highlights the code. An unlisted language renders as plain text.
stringtitle?React.ReactNode
Shows a header with this title.
React.ReactNodeicon?React.ReactNode
Shown before the title.
React.ReactNodeallowCopy?boolean
Show the copy button.
booleantrueallowWrap?boolean
Show the wrap toggle. Copying keeps the source either way.
booleantrueonCopy?() => void
Called once the code is on the clipboard.
() => voidlineNumbers?boolean
Number each line.
booleanfalsewrap?boolean
Whether long lines start wrapped. The wrap toggle changes it from there.
booleanfalsevariant?"default" | "destructive" | "terminal"
The tone of the block.
"default" | "destructive" | "terminal""default"size?"default" | "sm"
Use default or compact spacing.
"default" | "sm""default"width?"auto" | "sm" | "md" | "lg" | "full"
Caps the width of the block.
"auto" | "sm" | "md" | "lg" | "full""md"maxHeight?"sm" | "md" | "lg" | "none"
Caps the content at 300, 600, or 800 pixels, after which it scrolls.
"sm" | "md" | "lg" | "none""md"testId?string
`data-testid` for the block.
stringcopyTestId?string
`data-testid` for the copy button. Defaults to `${testId}-copy` when `testId` is set.
stringMigrating 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) 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 component and its
useHighlightedCodehook.