ComponentsMisc
Code block
Shows code or a log in a panel, with a copy button, line numbers and highlighting you bring.
Import
import { CodeBlock } from "@booleanpress/ui/code-block"Usage
Pass the code as plain text in code. The panel copies exactly that text.
import { CodeBlock } from "@booleanpress/ui/code-block"
export function Snippet() {
return <CodeBlock title="mailer.ts" code={source} language="ts" lineNumbers />
}title adds a bar above the code with the buttons in it; without one, the buttons sit in the top corner. lineNumbers numbers the lines in a gutter that is not selected or copied with the code. highlightLines marks lines by number, from 1. maxHeight caps the height, and the code area scrolls; wrapToggle adds a button that wraps long lines (or set wrap and onWrapChange yourself). copyable={false} removes the copy button.
The package has no highlighter. To colour the code, run Shiki or Prism yourself and pass its HTML as html, keeping the plain text in code for copying:
const html = await codeToHtml(source, { lang: "ts", theme: "github-light" })
<CodeBlock code={source} html={html} language="ts" />A highlighter's own <pre><code> wrapper is dropped, and an element that runs over several lines is closed and reopened on each, so line numbers and highlighted lines still work. The HTML is inserted without any cleaning, so it must be trusted: a highlighter's output is, as it escapes the code it colours, but HTML from anywhere else, above all from what a person typed, must be sanitised first. For code inside a sentence, use InlineCode from Typography.
Examples
Basic
Plain text with the copy button in the corner.
import { CodeBlock } from "@booleanpress/ui/code-block"
const CODE = `import { createMailer } from "@example/mail"
const mailer = createMailer({
host: "smtp.example.com",
port: 587,
})`
export default function CodeBlockBasic() {
return <CodeBlock code={CODE} language="ts" className="w-full max-w-lg" />
}With title
title adds a file-name bar that holds the buttons.
Line numbers
lineNumbers adds a gutter that is left out of selection and copying.
import { CodeBlock } from "@booleanpress/ui/code-block"
const CODE = `curl https://api.example.com/v1/messages \\
-H "Authorization: Bearer $API_KEY" \\
-H "Content-Type: application/json" \\
-d '{"to": "ada@example.com", "subject": "Welcome"}'`
export default function CodeBlockLineNumbers() {
return <CodeBlock title="Send a message" code={CODE} language="bash" lineNumbers className="w-full max-w-lg" />
}Highlighted lines
highlightLines={[3, 4]} fills those lines from edge to edge.
import { CodeBlock } from "@booleanpress/ui/code-block"
const CODE = `const mailer = createMailer({
host: "smtp.example.com",
port: 587,
secure: false,
auth: { user: "apikey", pass: process.env.SMTP_KEY },
})`
export default function CodeBlockHighlightedLines() {
return (
<CodeBlock
title="mailer.ts"
code={CODE}
language="ts"
lineNumbers
highlightLines={[3, 4]}
className="w-full max-w-lg"
/>
)
}Long (scrolls)
maxHeight makes the code area scroll, in both directions; wrapToggle wraps long lines.
import { CodeBlock } from "@booleanpress/ui/code-block"
const LINES = Array.from(
{ length: 24 },
(_, index) =>
`2026-10-0${(index % 9) + 1}T09:${String(10 + index).padStart(2, "0")}:00Z delivered msg_01J9X4T2Q${index} to=customer${index}@example.com mailer=primary latency=${120 + index * 7}ms`
)
export default function CodeBlockLong() {
return (
<CodeBlock
title="delivery.log"
code={LINES.join("\n")}
language="log"
lineNumbers
wrapToggle
maxHeight={240}
className="w-full max-w-lg"
/>
)
}Inline
InlineCode from Typography, for code in running text.
Set SMTP_KEY in your environment, then call createMailer() with the host and port your provider gives you.
import { InlineCode } from "@booleanpress/ui/typography"
export default function CodeBlockInline() {
return (
<p className="max-w-md text-sm/normal">
Set <InlineCode>SMTP_KEY</InlineCode> in your environment, then call <InlineCode>createMailer()</InlineCode> with the
host and port your provider gives you.
</p>
)
}Pre-highlighted HTML
html takes a highlighter's output; here a small hand-written one coloured with tokens.
import { CodeBlock } from "@booleanpress/ui/code-block"
const CODE = `// Retry a failed delivery
const result = await mailer.retry("msg_01J9X4T2QZ")
console.log(result.status)`
// What a highlighter returns, written by hand here: Shiki's or Prism's HTML goes in the same prop.
const HTML = [
`<span class="text-muted-foreground italic">// Retry a failed delivery</span>`,
`<span class="text-destructive-strong">const</span> result = <span class="text-destructive-strong">await</span> mailer.<span class="text-info-strong">retry</span>(<span class="text-success-tag-foreground">"msg_01J9X4T2QZ"</span>)`,
`console.<span class="text-info-strong">log</span>(result.status)`,
].join("\n")
export default function CodeBlockPreHighlighted() {
return <CodeBlock title="retry.ts" code={CODE} html={HTML} language="ts" lineNumbers className="w-full max-w-lg" />
}Accessibility
- Semantics
- A
<figure>, captioned by the title bar when there is one, holding a<pre>withrole="region"and a<code>. Line numbers are hidden from assistive technology. - Labels
- The code area is named from the provider's
codeBlockstring, "{title} code", filled withtitle, elselanguage; passaria-labelto name it yourself. The wrap toggle is named bywrapLinesand reports its state witharia-pressed; the copy button is CopyButton's. - Focus
- The wrap toggle and copy button come first in the tab order, as they sit first on screen, then the code area, so a keyboard can scroll it with the arrow keys; the panel shows the focus outline round its edge while the code area has focus.
- Known limits
- Highlighted lines are marked by colour only; a screen reader does not hear which lines they are. Say it in the text around the panel.
- Colours from
htmlare your highlighter's: check their contrast on--cardin both themes. htmlis inserted as HTML without cleaning: pass a highlighter's output or HTML you have sanitised, never raw HTML built from what a person typed.- Without a title the buttons sit over the code's top right-hand corner, on a right-to-left page too, as the code reads left to right; a first line that runs under them scrolls into view.
Keyboard
| Key | Behaviour |
|---|---|
| Tab | Moves to the wrap toggle and the copy button, then to the code area, where the arrow keys scroll it. |
| EnterorSpace | On the wrap toggle, wraps or unwraps long lines; on the copy button, copies the code. |
API
CodeBlock
Renders a figure and passes it every other prop.
| Prop | Type | Default | Description |
|---|---|---|---|
coderequired | string | The code as plain text: what it shows (unless html is given) and what the copy button copies. | |
copyable | boolean | true | Shows the copy button. true by default. |
defaultWrap | boolean | false | Whether long lines start wrapped, when it controls itself. |
highlightLines | number[] | Line numbers, from 1, to mark with the hovered-surface fill. | |
html | string | The same code, highlighted, as HTML: what Shiki's codeToHtml or Prism's highlight returns. It is inserted as HTML
without any cleaning, so it must be trusted: a highlighter's output is (it escapes the code it colours); HTML from
anywhere else, above all from what a person typed, must be sanitised before it gets here. | |
language | string | The language, set as data-language and a language-* class on the <code>, and naming the region without a title. | |
lineNumbers | boolean | false | Numbers each line, in a gutter that is not selected or copied with the code. |
maxHeight | string | number | The tallest the code area grows before it scrolls: px as a number, or any CSS length. | |
onWrapChange | ((wrap: boolean) => void) | Called with true or false when the wrap toggle is pressed. | |
title | string | A file name or caption, shown in a bar above the code. | |
wrap | boolean | Whether long lines wrap, when you control it. Pair it with onWrapChange. | |
wrapToggle | boolean | false | Shows a toggle button that wraps and unwraps long lines. |
Every part takes className, merged with its defaults by cn(), and ref, which reaches the element it renders.
Data attributes: data-slot="code-block-actions" (CodeBlock), and data-wrap, data-language, data-highlighted.
Provider strings: codeBlock, wrapLines (BooleanUIProvider's strings).
Theming
The panel is --card with the border token and an 8 px radius; line numbers are --muted-foreground, highlighted lines --accent. Code uses font-mono: set --font-mono in your theme for another typeface.
The tokens its classes read. Change their values in your theme, and it follows.
| Token | Used for |
|---|---|
--accent | background |
--accent-foreground | text |
--card | background |
--card-foreground | text |
--foreground | text |
--muted-foreground | text |
--ring | outline |