# Code Block
A syntax-highlighted code block component powered by Shiki. Supports static code strings and streaming content, line numbers, and copy-to-clipboard.
```tsx demo
import typescript from "@shikijs/langs/typescript";
import { CodeBlock } from "@tenstorrent/vesper/code-block";
export default function CodeBlockDemo() {
return (
{'const greeting = "Hello, world!";\nconsole.log(greeting);'}
);
}
```
> [!NOTE]
>
> The code samples on this page are rendered using the `CodeBlock` component itself!
## Options
| Prop | Type | Description | Default |
| ----------------- | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `children` | `string \| () => ReadableStream \| Promise>` | The code to render. Can be a string or a factory function returning a `ReadableStream` for streaming content. | `""` |
| `lang` | `LanguageRegistration[] \| "text" \| "ansi"` | The language syntax for highlighting. Import grammar objects from `@shikijs/langs`. Use `"text"` for plain text or `"ansi"` for ANSI escape codes. | `"text"` |
| `showLineNumbers` | `boolean` | Whether to show line numbers on the left-hand side. | `false` |
| `copyOnHover` | `boolean` | When `true`, hides the copy-to-clipboard button until the code block is hovered. | `false` |
All other props are forwarded to the underlying `
` element.
To render a short snippet of code inline with other text, reach for [the `Code` component](./code.mdx), and for a single copyable command, [the `Snippet` component](./snippet.mdx).
## Examples
### Basic usage
Render a `CodeBlock` by passing it plain text. Without a `lang`, the code is rendered as-is with no syntax highlighting. Every code block renders a copy-to-clipboard button in its top-right corner, which copies the code exactly as it was passed in:
```tsx demo
import { CodeBlock } from "@tenstorrent/vesper/code-block";
export default function BasicCodeBlock() {
return Hello, world!;
}
```
### Syntax highlighting
`CodeBlock` ships without any built-in grammars, and expects you to provide them yourself. Individual grammars can be imported from `@shikijs/langs/*` and supplied to the component. For example, to achieve `javascript` syntax highlighting you would import the grammar and pass it to the component via the `lang` prop:
```tsx demo
import javascript from "@shikijs/langs/javascript";
import { CodeBlock } from "@tenstorrent/vesper/code-block";
export default function HighlightedCodeBlock() {
return (
{"const count = 42;\nconsole.log(count);"}
);
}
```
> [!NOTE]
>
> Importing grammars individually keeps your bundle to only the languages you actually render.
#### Plain text and ANSI
Plain text and ANSI come prebundled and don't require supplying any grammar objects. You can simply set the `lang` prop to `"text"` or `"ansi"`:
```tsx demo
import { CodeBlock } from "@tenstorrent/vesper/code-block";
const BUILD_LOGS = [
"\u001b[1;36m● vesper\u001b[0m \u001b[2mv0.2.0\u001b[0m",
"\u001b[32m✓\u001b[0m compiled 42 modules",
"\u001b[33m!\u001b[0m 1 warning found",
"\u001b[31m✗\u001b[0m failed to resolve \u001b[1m./missing-module\u001b[0m",
].join("\n");
export default function AnsiCodeBlock() {
return {BUILD_LOGS};
}
```
This is what you want for output captured from a terminal, such as build logs or test runs, where the color information is already encoded in the text itself.
#### Custom grammars
You can provide your own custom grammars by passing them into the `lang` prop as an array of TextMate grammar objects:
```tsx
import { CodeBlock } from "@tenstorrent/vesper/code-block";
import customGrammar from "./custom-grammar.json";
export default function CustomGrammarCodeBlock({ code }: { code: string }) {
return {code};
}
```
[tm-grammars package on GitHub](https://github.com/shikijs/textmate-grammars-themes/tree/main/packages/tm-grammars)
### With line numbers
Passing `showLineNumbers` renders a line number gutter down the left-hand side of the code block:
```tsx demo
import python from "@shikijs/langs/python";
import { CodeBlock } from "@tenstorrent/vesper/code-block";
const CODE = `def greet(name: str) -> str:
message = f"Hello, {name}!"
print(message)
return message
greet("world")`;
export default function NumberedCodeBlock() {
return (
{CODE}
);
}
```
Line numbers are rendered decoratively, so they are never included in the text copied to the clipboard.
### Streaming content
To stream code as it arrives (build logs, LLM output, etc.), pass a factory function that returns a `ReadableStream` instead of a string. Tokens are highlighted as they arrive, and the block scrolls to follow the output unless the user scrolls away from the bottom:
```tsx demo
import { useState } from "react";
import typescript from "@shikijs/langs/typescript";
import { Button } from "@tenstorrent/vesper/button";
import { CodeBlock } from "@tenstorrent/vesper/code-block";
const CODE = `async function getUser(id: string) {
const response = await fetch(\`/api/users/\${id}\`);
if (!response.ok) throw new Error("Request failed");
return response.json();
}`;
/**
* A factory is used instead of a raw stream because a stream can only be read
* once: this way the component can create a fresh one whenever it needs to
*/
function createCodeStream() {
let remaining = CODE;
return new ReadableStream({
async start(controller) {
while (remaining.length) {
controller.enqueue(remaining.slice(0, 8));
remaining = remaining.slice(8);
await new Promise((resolve) => setTimeout(resolve, 60));
}
controller.close();
},
});
}
export default function StreamedCodeBlock() {
const [run, setRun] = useState(0);
return (
{createCodeStream}
);
}
```
### Showing clipboard button on hover
The copy-to-clipboard button is always visible by default; pass `copyOnHover` to hide it until the code block is hovered:
```tsx demo
import json from "@shikijs/langs/json";
import { CodeBlock } from "@tenstorrent/vesper/code-block";
const CODE = `{
"name": "@tenstorrent/vesper",
"version": "0.2.0"
}`;
export default function HoverCopyCodeBlock() {
return (
{CODE}
);
}
```