Code block
Code with line numbers, highlighting, and a copy button, highlighted on the server so it paints in color.
When to use it
Use Code Block for any code longer than a line. CodeBlockFile highlights it with Pierreโs themes on the JZB code surface.
For a word or two of code in a sentence, use inline code instead.
Examples
$ pnpm build
> vite build
โ 1284 modules transformed.
dist/index.html 0.46 kB
dist/assets/index-4f2a9c.css 18.92 kB
dist/assets/index-b81e07.js 142.31 kB
โ built in 2.41s123456789101112131415161718import { CodeBlock, CodeBlockActions, CodeBlockContent, CodeBlockCopyButton, CodeBlockHeader, CodeBlockTitle} from "@/components/ui/code-block"
<CodeBlock> <CodeBlockHeader> <CodeBlockTitle>Terminal</CodeBlockTitle> <CodeBlockActions> <CodeBlockCopyButton value={output} /> </CodeBlockActions> </CodeBlockHeader> <CodeBlockContent>{output}</CodeBlockContent></CodeBlock>export function Greeting({ name }: { name: string }) { return <p>Hello, {name}</p> return <p className="body-md">Hello, {name}!</p>}12345678910111213import { CodeBlock, CodeBlockContent, CodeBlockHeader, CodeBlockLine, CodeBlockTitle } from "@/components/ui/code-block"
<CodeBlock> <CodeBlockHeader> <CodeBlockTitle>greeting.tsx</CodeBlockTitle> </CodeBlockHeader> <CodeBlockContent lineNumbers> <CodeBlockLine>{"export function Greeting({ name }: { name: string }) {"}</CodeBlockLine> <CodeBlockLine variant="removed">{" return <p>Hello, {name}</p>"}</CodeBlockLine> <CodeBlockLine variant="added">{" return <p className=\"body-md\">Hello, {name}!</p>"}</CodeBlockLine> <CodeBlockLine>{"}"}</CodeBlockLine> </CodeBlockContent></CodeBlock>12345678910import { ThemeProvider } from "@/components/theme-provider"import { Dashboard } from "./dashboard"
export function App() { return ( <ThemeProvider defaultTheme="system"> <Dashboard /> </ThemeProvider> )}123456789<CodeBlock> <CodeBlockHeader> <CodeBlockTitle>app.tsx</CodeBlockTitle> <CodeBlockActions> <CodeBlockCopyButton value={code} /> </CodeBlockActions> </CodeBlockHeader> <CodeBlockFile code={code} filename="app.tsx" highlightLines={[6, 7, 8]} /></CodeBlock>12345678910111213import { ScrollArea } from "@/components/ui/scroll-area"
<CodeBlock> <CodeBlockHeader> <CodeBlockTitle>use-search.ts</CodeBlockTitle> <CodeBlockActions> <CodeBlockCopyButton value={code} /> </CodeBlockActions> </CodeBlockHeader> <ScrollArea className="max-h-64"> <CodeBlockFile code={code} filename="use-search.ts" /> </ScrollArea></CodeBlock>TypeError: Cannot read properties of undefined (reading 'map')
at ProjectList (src/components/project-list.tsx:18:22)
at renderWithHooks (react-dom.development.js:16305:18)123456789<CodeBlock variant="destructive" size="sm"> <CodeBlockHeader> <CodeBlockTitle>Build failed</CodeBlockTitle> <CodeBlockActions> <CodeBlockCopyButton value={error} /> </CodeBlockActions> </CodeBlockHeader> <CodeBlockContent wrap>{error}</CodeBlockContent></CodeBlock>[01] GET /api/projects 200 in 12ms
[02] GET /api/projects 200 in 19ms
[03] GET /api/projects 200 in 26ms
[04] GET /api/projects 200 in 33ms
[05] GET /api/projects 200 in 40ms
[06] GET /api/projects 200 in 47ms
[07] GET /api/projects 200 in 14ms
[08] GET /api/projects 200 in 21ms
[09] GET /api/projects 200 in 28ms
[10] GET /api/projects 200 in 35ms
[11] GET /api/projects 200 in 42ms
[12] GET /api/projects 200 in 49ms
[13] GET /api/projects 200 in 16ms
[14] GET /api/projects 200 in 23ms
[15] GET /api/projects 200 in 30ms
[16] GET /api/projects 200 in 37ms
[17] GET /api/projects 200 in 44ms
[18] GET /api/projects 200 in 51ms
[19] GET /api/projects 200 in 18ms
[20] GET /api/projects 200 in 25ms
[21] GET /api/projects 200 in 32ms
[22] GET /api/projects 200 in 39ms
[23] GET /api/projects 200 in 46ms
[24] GET /api/projects 200 in 13ms12345678910111213141516171819202122232425const [expanded, setExpanded] = useState(false)const id = useId()
<CodeBlock> <CodeBlockHeader> <CodeBlockTitle>Request log</CodeBlockTitle> </CodeBlockHeader> <CodeBlockContent id={id} className={expanded ? "max-h-96 scroll-fade-y" : "max-h-48 overflow-y-hidden scroll-fade-y"} > {log} </CodeBlockContent> <CodeBlockFooter className="justify-center"> <Button variant="ghost" size="xs" aria-expanded={expanded} aria-controls={id} onClick={() => setExpanded(!expanded)} > {expanded ? "Show less" : "Show more"} </Button> </CodeBlockFooter></CodeBlock>How it behaves
Compose the block from parts: CodeBlockHeader holds a CodeBlockTitle and CodeBlockActions, and CodeBlockFooter sits below the content. Without a header, put CodeBlockActions directly in CodeBlock: they float at the top end, show on hover or keyboard focus, stay while one of their menus is open, and always show on touch screens.
CodeBlockFile renders highlighted code with @pierre/diffs. Pass code with a language, or a filename to infer one. Pierre Light (light mode) and Pierre Dark Soft (dark mode) color the tokens; pass theme with any Pierre variant or Shiki theme. The surface, border, code font, and size stay on the JZB code tokens. Pass highlightLines to call out lines by number, and wrap the file in ScrollArea with a max-h-* class to cap a long one.
CodeBlockContent renders plain text in a pre without loading the highlighter, for terminal output, logs, and streaming text. Add lineNumbers and startLine to number it, wrap to wrap long lines, or a max-h class to cap it; its edges fade as it scrolls. Render CodeBlockLine children to mark lines added, removed, or highlighted; each tint fades across the line, and highlighted lines match CodeBlockFile's accent bar.
CodeBlockLanguageSelect and CodeBlockDisplayMenu are optional actions for a CodeBlockFile; keep their state yourself and pass it to the file. CodeBlockCopyButton copies its value, shows a tooltip, and announces Copied in a polite live region.
Code always reads left to right. CodeBlockContent is focusable so keyboard users can scroll it, and line numbers and diff markers are left out of a copied selection.
Without prerendered HTML, CodeBlockFile shows numbered plain code until the highlighter loads in the browser. Highlight on the server with preloadCodeBlock to paint colors on the first load; see Highlight on the server above. While a new language or display setting loads, prerendered code keeps its current view instead of flashing plain.
Build it
Add it with the shadcn CLI:
1pnpm dlx shadcn@latest add https://jzbui.com/r/code-block.jsonA complete example. Change the values to fit your product:
12345678910111213141516171819202122import { CodeBlock, CodeBlockActions, CodeBlockCopyButton, CodeBlockFile, CodeBlockHeader, CodeBlockTitle} from "@/components/ui/code-block"
const code = `export function search(query: string) { return fetch("/api/search?q=" + encodeURIComponent(query))}`
<CodeBlock> <CodeBlockHeader> <CodeBlockTitle>search.ts</CodeBlockTitle> <CodeBlockActions> <CodeBlockCopyButton value={code} /> </CodeBlockActions> </CodeBlockHeader> <CodeBlockFile code={code} filename="search.ts" /></CodeBlock>Highlight on the server
On its own, CodeBlockFile highlights in the browser once its JavaScript has loaded. Until then it shows the code with line numbers but without colors. To paint highlighted code on the first load, highlight it on the server with preloadCodeBlock and pass the result as prerenderedHTML. This page does that while it is prerendered.
123456789101112131415161718192021import { createFileRoute } from "@tanstack/react-router"import { createServerFn } from "@tanstack/react-start"import { CodeBlock, CodeBlockFile } from "@/components/ui/code-block"import { preloadCodeBlock } from "@/lib/code-block-server"
const example = { code: source, language: "typescript" } as const
const highlight = createServerFn({ method: "GET" }).handler(() => preloadCodeBlock(example))
export const Route = createFileRoute("/guide")({ loader: () => highlight(), component: Guide})
function Guide() { return ( <CodeBlock> <CodeBlockFile {...example} prerenderedHTML={Route.useLoaderData()} /> </CodeBlock> )}1234567891011121314// app/guide/page.tsx is a server componentimport { CodeBlock, CodeBlockFile } from "@/components/ui/code-block"import { preloadCodeBlock } from "@/lib/code-block-server"
export default async function Page() { const example = { code: source, language: "typescript" } as const const prerenderedHTML = await preloadCodeBlock(example)
return ( <CodeBlock> <CodeBlockFile {...example} prerenderedHTML={prerenderedHTML} /> </CodeBlock> )}Pass the same settings to both: code, language or filename, line numbers, wrapping, and theme. Different settings canโt reuse the prerendered markup, and client-side navigation without it falls back to highlighting in the browser.
The full source is yours to edit once installed: