Skip to main content
NachUI

Code Block

A code block with a copy button, optional line numbers and a fold for long snippets. No highlighter attached.

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

export function Snippet() {
  return (
    <CodeBlock code={source} language="tsx">
      <CodeBlock.CopyButton />
      <CodeBlock.Content />
    </CodeBlock>
  );
}
1'use client';
2
3import { CodeBlock } from '@/components/ui/code-block';
4
5const CODE = `import { CodeBlock } from '@/components/ui/code-block';
6
7export function Snippet() {
8 return (
9 <CodeBlock code={source} language="tsx">
10 <CodeBlock.CopyButton />
11 <CodeBlock.Content />
12 </CodeBlock>
13 );
14}`;
15
16export function Default() {
17 return (
18 <div className="w-full max-w-lg">
19 <CodeBlock code={CODE} language="tsx" showLineNumbers>
20 <CodeBlock.CopyButton />
21 <CodeBlock.Content />
22 </CodeBlock>
23 </div>
24 );
25}

Installation

pnpm dlx nachui add code-block

Anatomy

1import { CodeBlock } from '@/components/ui/code-block';
1<CodeBlock code={source} language="tsx" showLineNumbers>
2 <CodeBlock.CopyButton />
3 <CodeBlock.Content />
4</CodeBlock>
The code goes in as a string on the root, and every part reads it from there. There is no header: the copy button floats in the top right corner and shows on hover, so the block is just the code.

Composition

Use the following composition to build a CodeBlock:
CodeBlock
├── CodeBlock.CopyButton
├── CodeBlock.Content
└── CodeBlock.Expand

Folding long code

Set collapsible and the content stops at maxLines. CodeBlock.Expand renders the toggle, and only when there is something to reveal.
export const steps = [
  { id: 1, label: 'Step 1', done: true },
  { id: 2, label: 'Step 2', done: true },
  { id: 3, label: 'Step 3', done: true },
  { id: 4, label: 'Step 4', done: true },
  { id: 5, label: 'Step 5', done: true },
  { id: 6, label: 'Step 6', done: true },
  { id: 7, label: 'Step 7', done: true },
1'use client';
2
3import { CodeBlock } from '@/components/ui/code-block';
4
5const CODE = Array.from({ length: 24 }, (_, index) =>
6 index === 0
7 ? 'export const steps = ['
8 : index === 23
9 ? '];'
10 : ` { id: ${index}, label: 'Step ${index}', done: ${index < 12} },`,
11).join('\n');
12
13export function Collapsible() {
14 return (
15 <div className="w-full max-w-lg">
16 <CodeBlock code={CODE} language="ts" collapsible maxLines={8}>
17 <CodeBlock.CopyButton />
18 <CodeBlock.Content />
19 <CodeBlock.Expand />
20 </CodeBlock>
21 </div>
22 );
23}

Syntax highlighting

There is none built in, on purpose: a highlighter is the one dependency that would double the size of this file. Pass renderLine and return whatever your highlighter gives you for each line, Shiki tokens included.
1<CodeBlock code={source} renderLine={(line, index) => highlight(line, index)}>
2 <CodeBlock.Content />
3</CodeBlock>

API Reference

CodeBlock

PropTypeDefaultDescription
codestring-The source to display and copy
languagestring-Set on data-language and as language-* on the code
showLineNumbersbooleanfalseAdds a gutter with line numbers
collapsiblebooleanfalseStops the content at maxLines until expanded
maxLinesnumber20Lines shown while collapsed
renderLine(line: string, index: number) => ReactNode-Renders each line, for plugging in a highlighter
classNamestring-Additional CSS classes

CodeBlock.CopyButton

PropTypeDefaultDescription
copyLabelstring'Copy code'Accessible name at rest
copiedLabelstring'Copied'Accessible name for 1.5s after copying
onCopied(code: string) => void-Called after the clipboard write
classNamestring-Additional CSS classes

CodeBlock.Content

PropTypeDefaultDescription
classNamestring-Additional CSS classes

CodeBlock.Expand

PropTypeDefaultDescription
expandLabel(hidden: number) => stringShow {n} more linesLabel while collapsed
collapseLabelstring'Show less'Label while expanded
classNamestring-Additional CSS classes
Found something to improve?

Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.

Create an Issue