Code Block
Code blocks render and apply syntax highlighting to blocks of code. The mantleCodeBlockPlugins() Vite plugin highlights the code at build time with Shiki, so there is zero highlighting cost in the browser.
Use mantleCode("language") tagged template literals to define code values. The Vite plugin transforms these at build time, inlining pre-rendered HTML.
ngrok-example.js
Examples
Single line with a header
Many code blocks will be single line command line prompts and should be able to render with a header and copy button. This makes it absolutely clear that this example is a command line prompt and not a code sample.
Command Line
Horizontal scrolling
This example is included to demonstrate that code blocks can scroll horizontally if the content is too wide. Mantle attempts to normalize scrollbar styling across browsers and platforms.
When line numbers are shown, the gutter (numbers and fold toggles) stays pinned to the left edge while the code scrolls under it.
ngrok-example.js
No header or copy button
This is the most simple example of our code block component. While very useful, the copy button is optional. It is also perfectly acceptable to render a code block without a header, especially if the surrounding content carries the context or the code block is self-explanatory eg. "In your index.js file, paste the following:".
Single line with horizontal scrolling
This example is included to show the interaction between the copy button and horizontal scrolling on a single verbose terminal command.
Server-rendered syntax highlighting
The CodeBlock supports server-rendered syntax highlighting for dynamic or user-supplied code. Use createMantleCodeBlockValue() to construct a value from server-highlighted HTML and pass it to CodeBlock.Code. This is useful when the code to highlight isn't known at build time (for example, user input or API responses).
Highlight microservice (for Go / non-Node.js backends)
For frontends backed by non-Node.js services (Go, Python, etc.), deploy a small Node.js highlight service as a sidecar or shared microservice. This gives you the same server-rendered highlighting without shipping Shiki to the browser; the frontend calls the API with fetch.
Create a highlight server using createMantleServerSyntaxHighlighter:
Then use it from any frontend the same way as the server-rendered example above, pointing at the highlight service URL:
Foldable code blocks
Code blocks automatically render editor-style fold carets in the gutter for any block whose language has a folding strategy: AST-based (JS/TS/JSX/TSX, HTML, XML), raw-source (JSON, CSS), bracket-paired (Go, Java, C#, Rust, Terraform), indentation-based (Python, YAML), keyword-paired (Bash/Shell), or bracket + keyword (Ruby). Click the caret (or focus and press Enter / Space) to collapse the inner lines; the opener and closer lines stay visible, and a ⋯ placeholder marks the collapsed region.
Folding is computed before the block reaches the browser, and the entire interaction runs through a single delegated click handler per code block: no per-line listeners and no React re-renders on toggle. This keeps fold/unfold cheap even for code blobs with thousands of lines.
The mantleCode() Vite transform, MDX code fences, and createMantleServerSyntaxHighlighter() use the full server-side folding dispatcher. The lower-level computeFoldRanges({ language, tokens }) export from @ngrok/mantle/highlight-utils is intentionally token-only for custom integrations; it covers bracket, indentation, and tag folds only. Use @ngrok/mantle-server-syntax-highlighter for AST, raw-source, or keyword strategies.
For one example per supported language plus a list of caveats, see folding by language.
package.json
Highlighting runtime JSON on the client
mantleCode() highlights at build time, so it can't highlight a value you only have at runtime: an API response, a table row's underlying record, anything fetched or computed in the browser. For that, use jsonCodeBlockValue(value): it serializes the value with JSON.stringify (2-space indent), tokenizes it, and returns a MantleCodeBlockValue you pass straight to CodeBlock.Code, entirely on the client, with no Shiki runtime, grammar, or WASM shipped to the browser (~1 KB). Because Mantle highlights with a CSS-variables theme the colors already live in CSS, so only tokenization happens at runtime; the output is byte-for-byte identical to a server/build-time highlighted block and adapts to light/dark themes for free.
Multi-line objects and arrays are collapsible by default (the same fold runtime as every other JSON block), so pass { foldable: false } for a flat panel, or { showLineNumbers: true } for a gutter. Serialization is safe and never throws: BigInt renders as its decimal string, circular/repeated references collapse to "[Circular]", and values that serialize to undefined render as an empty string. It's ideal for inspecting a record inside a DataTable.ExpandedRow.
For lower-level control, jsonToShikiHtml(code) returns only the Shiki-identical inner HTML for a <code> element (no line-number or fold decoration); jsonCodeBlockValue builds on it.
Tabbed code block
Use CodeBlock.TabList, CodeBlock.TabTrigger, and CodeBlock.TabContent to create a tabbed code block. Pass defaultTab (or activeTab / onActiveTabChange for controlled mode) to CodeBlock.Root, place the tab triggers in the CodeBlock.Header, and wrap each CodeBlock.Code in a CodeBlock.TabContent. The copy button automatically copies whichever code is currently displayed.
When the tabs are too wide for the header, CodeBlock.TabList scrolls horizontally with a scroll-fade-x edge fade instead of wrapping onto a second row (no extra wiring required).
Reusing fragments with nested interpolation
A mantleCode value can be embedded into another mantleCode template by interpolating its .code property. When the Vite plugin recognizes the reference, it inlines the fragment's source at build time, so the embedded code is highlighted line-for-line in its host language (correct line numbers, folding, and copy text) with no runtime substitution cost.
This is ideal for sharing a Traffic Policy or config snippet across multiple SDK examples without duplicating it. The fragment can live at the top level of the same module, or be imported from another module:
The fragment must be a top-level mantleCode const referenced as .code: either declared earlier in the same module, or a named import of a directly-exported top-level const from another module. Nested fragments are resolved recursively. Interpolations that can't be resolved statically (dynamic values, re-exports, bare identifiers without .code) fall back to runtime placeholder substitution, and an unresolved imported .code reference emits a build warning. The embedded source is highlighted with the outer language's grammar (e.g. the YAML above is highlighted as Java text-block content, not as YAML).
mantleCode() options
The mantleCode() tagged template accepts an optional second argument with the following options:
showLineNumbers: Whether to show line numbers. Defaults totruefor most languages, butfalsefor single-line shell snippets (bash,sh,shell). Passfalseto hide them ortrueto force them on.highlightLines: An array of line numbers or ranges (e.g.[2, "4-5"]) to visually highlight.lineNumberStart: The starting line number when line numbers are displayed. Defaults to1.indentation: Override the default indentation style ("tabs"or"spaces").
all-options.ts
Overriding defaults
The mantleCode() options let you customize how code is displayed. By default, line numbers are shown starting at 1 and indentation is inferred from the language. You can also specify highlighted lines per code block. The first example below uses the defaults (YAML auto-detects space indentation), while the second overrides all options: custom indentation, line number start, highlighted lines, and visible line numbers.
traffic-policy.yaml
ngrok-example.js (all options overridden)
Composition
Compose the parts of a CodeBlock together to build your own:
Polymorphism
CodeBlock.Root, CodeBlock.Header, CodeBlock.Title, CodeBlock.Body, and CodeBlock.CopyButton accept asChild. When true the part renders its single child instead of its default element, forwarding class names, data-* attributes, and ref onto it.
Reach for it when Root should be a semantic <figure> or <section>, when Title needs a different heading level than the default <h3>, or when Header and Body should be <header> and <section>. CopyButton has no children of its own, so it is typed SelfClosingWithAsChild: <CodeBlock.CopyButton asChild> requires exactly one child to clone, and forbids children otherwise.
The remaining parts do not accept asChild. CodeBlock.Code owns the highlighted markup it renders, and CodeBlock.TabList, CodeBlock.TabTrigger, and CodeBlock.TabContent each explicitly omit the prop because their behavior and mounting are driven by the active tab. CodeBlock.Icon takes its element through the svg prop instead. CodeBlock.ExpanderButton renders its own "Show more" / "Show less" label and caret icon, so there is no single child to clone; style it with className.
Translation
CodeBlock.Code and CodeBlock.Title carry translate="no", so a browser translation engine skips the code and the filename. A translated CLI flag, env var, or YAML key is wrong, and the reader copies it anyway. A translated example.ts names a file that does not exist.
The attribute also protects the render itself: Google Translate replaces each text node with a <font> wrapper, which detaches the text nodes React holds, so React either writes updates into a node the DOM no longer shows, or throws NotFoundError from removeChild and tears down the root.
The two parts differ on whether a call site may turn the guard off. CodeBlock.Code omits the translate prop from its type, because a block of code is never translatable. CodeBlock.Title keeps it, because the slot takes arbitrary children; pass translate="yes" for a title that is prose rather than a filename.
Accessibility
CodeBlock.CopyButtonis an icon button named bylabel(default "Copy code"). After a copy, an always-mountedrole="status"element beside the button announces "Copied", so a screen-reader user hears the result and not only the icon swap.CodeBlock.ExpanderButtoncarriesaria-expandedandaria-controlsthat points at the<pre>. The<pre>itself carries no ARIA state;data-state="collapsed" | "expanded"drives the styling.- Fold toggles are real
<button>elements named "Toggle code folding" witharia-expanded. Enter and Space toggle them. The folded marker isaria-hidden. - Line numbers are
aria-hidden, so a screen reader reads the code, not "1 const x". CodeBlock.CodeandCodeBlock.Titlecarrytranslate="no"; see Translation.
API Reference
The CodeBlock renders and applies syntax highlighting to blocks of code and is composed of several sub-components.
CodeBlock.Root
Root container for all CodeBlock sub-components. For tabbed code blocks, pass defaultTab (uncontrolled) or activeTab / onActiveTabChange (controlled) to enable tab switching.
All props from standard HTML div attributes, plus:
CodeBlock.Body
The body of the CodeBlock. This is where the CodeBlock.Code and optional CodeBlock.CopyButton are rendered as direct children.
All props from standard HTML div attributes, plus:
CodeBlock.Code
The CodeBlock content. This is where the code is rendered with pre-highlighted Shiki HTML.
The <pre> always carries translate="no".
All props from standard HTML pre attributes except translate, plus:
Inside the highlighted HTML, data-slot="line-number", data-slot="fold-toggle", and data-slot="fold-ellipsis" mark the gutter number, the fold button, and the folded marker.
CodeBlock.Header
An optional header slot of the CodeBlock. This is where things like the CodeBlock.Icon and CodeBlock.Title are rendered.
All props from standard HTML div attributes, plus:
CodeBlock.Title
The (optional) title of a CodeBlock. Default renders as an h3 element; use asChild to render something else.
The heading carries translate="no" by default, because the title usually names a file.
All props from standard HTML h3 attributes, plus:
CodeBlock.CopyButton
The optional copy button. Render it as a child of CodeBlock.Body. It copies the raw source of the mounted CodeBlock.Code to the clipboard — in a tabbed block, the active tab's code.
All props from standard HTML button attributes, plus:
After a copy, an always-mounted role="status" element inside the part announces "Copied".
CodeBlock.ExpanderButton
The (optional) expander button of the CodeBlock. Render this as a child of the CodeBlock.Root to allow users to expand/collapse the code block contents. It carries aria-expanded and aria-controls that points at the <pre>. It does not take asChild: it renders its own label and caret, so style it with className.
All props from standard HTML button attributes except children, aria-controls, and aria-expanded.
CodeBlock.Icon
A small icon that represents the type of code block being displayed, rendered as an SVG next to the code block title in the code block header. You can pass in a custom SVG component or use one of the presets (you can exclusively pass one of svg or preset).
All props from Icon, plus:
CodeBlock.TabList
A tab list for the CodeBlock header. Renders pill-styled tab triggers that switch which code is displayed. Place this inside CodeBlock.Header and pair with CodeBlock.TabContent in CodeBlock.Body. CodeBlock.Root owns the tab state via defaultTab / activeTab / onActiveTabChange.
All props from standard HTML div attributes.
CodeBlock.TabTrigger
A pill-styled tab trigger for the CodeBlock header. Must be rendered within a CodeBlock.TabList.
All props from standard HTML button attributes, plus:
CodeBlock.TabContent
Conditionally renders its children when the associated tab is active. Pair with CodeBlock.TabList and CodeBlock.TabTrigger in the header, and set defaultTab / activeTab on CodeBlock.Root.
All props from standard HTML div attributes, plus:
jsonCodeBlockValue
Builds a MantleCodeBlockValue that renders any value as syntax-highlighted JSON entirely on the client — no Shiki runtime, build-time plugin, or server roundtrip (see Highlighting runtime JSON on the client). Pass the result to CodeBlock.Code's value prop. Serialization is safe and never throws: BigInt → decimal string, circular/repeated references → "[Circular]", and values that serialize to undefined → "".
Byte-for-byte parity with server/build-time Shiki output is guaranteed for canonical JSON.stringify shapes — which is exactly what this function produces.
jsonToShikiHtml
The lower-level primitive behind jsonCodeBlockValue. Tokenizes a JSON string into the same CSS-variable-colored <code> inner HTML that Mantle's server/build-time Shiki highlighter produces, without shipping Shiki to the browser. Returns an HTML string with no line-number or fold decoration — feed it through decorateHighlightedHtml for that, as jsonCodeBlockValue does. Reach for it only when you need the raw markup; most consumers want jsonCodeBlockValue.