Code blocks render and apply syntax highlighting to blocks of code. Syntax highlighting is performed at build time using Shiki via the mantleCodeBlockPlugins() Vite plugin, 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.
const listener = await ngrok.connect({ // session configuration addr: `localhost:8080`, // or `8080` or `unix:${UNIX_SOCKET}` authtoken: "<authtoken>", authtoken_from_env: true, on_status_change: (addr, error) => { console.log(`disconnected, addr ${addr} error: ${error}`); }, session_metadata: "Online in One Line", // listener configuration allow_user_agent: "^mozilla.*", basic_auth: ["ngrok:online1line"], circuit_breaker: 0.1, compression: true, deny_user_agent: "^curl.*", domain: "<domain>", ip_restriction_allow_cidrs: ["0.0.0.0/0"], ip_restriction_deny_cidrs: ["10.1.1.1/32"], metadata: "example listener metadata from nodejs", mutual_tls_cas: [fs.readFileSync('ca.crt', 'utf8')], oauth_provider: "google", oauth_allow_domains: ["<domain>"], oauth_allow_emails: ["<email>"], oauth_scopes: ["<scope>"], oauth_client_id: "<id>", oauth_client_secret: "<secret>", oidc_issuer_url: "<url>", oidc_client_id: "<id>", oidc_client_secret: "<secret>", oidc_allow_domains: ["<domain>"], oidc_allow_emails: ["<email>"], oidc_scopes: ["<scope>"], proxy_proto: "", // One of: "", "1", "2" request_header_remove: ["X-Req-Nope"], response_header_remove: ["X-Res-Nope"], request_header_add: ["X-Req-Yup:true"], response_header_add: ["X-Res-Yup:true"], schemes: ["HTTPS"], verify_webhook_provider: "twilio", verify_webhook_secret: "asdf", websocket_tcp_converter: true,});1import { CodeBlock, mantleCode } from "@ngrok/mantle/code-block";2 3<CodeBlock.Root>4 <CodeBlock.Header>5 <CodeBlock.Icon preset="file" />6 <CodeBlock.Title>…</CodeBlock.Title>7 </CodeBlock.Header>8 <CodeBlock.Body>9 <CodeBlock.CopyButton />10 <CodeBlock.Code value={mantleCode("javascript")`…`} />11 </CodeBlock.Body>12 <CodeBlock.ExpanderButton />13</CodeBlock.Root>;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.
sudo unzip ~/Downloads/ngrok-v3-stable-darwin.zip -d /usr/local/bin1<CodeBlock.Root>2 <CodeBlock.Header>3 <CodeBlock.Icon preset="cli" />4 <CodeBlock.Title>Command Line</CodeBlock.Title>5 </CodeBlock.Header>6 <CodeBlock.Body>7 <CodeBlock.CopyButton />8 <CodeBlock.Code9 value={mantleCode(10 "bash",11 )`sudo unzip ~/Downloads/ngrok-v3-stable-darwin.zip -d /usr/local/bin`}12 />13 </CodeBlock.Body>14</CodeBlock.Root>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.
const http = require('http');const ngrok = require("@ngrok/ngrok");const server = http.createServer((req, res) => { res.writeHead(200); res.end("Hello!"); setTimeout(() => { Promise.resolve().then(() => { console.log("url:", server.tunnel.url()); }); }, timeout);});// Consumes authtoken from env automaticallyngrok.listen(server).then(() => { console.log("url:", server.tunnel.url());});// really long line here that should wrap around and stuff Officia ipsum sint eu labore esse deserunt aliqua quis irure.1<CodeBlock.Root>2 <CodeBlock.Header>3 <CodeBlock.Icon preset="file" />4 <CodeBlock.Title>ngrok-example.js</CodeBlock.Title>5 </CodeBlock.Header>6 <CodeBlock.Body>7 <CodeBlock.CopyButton />8 <CodeBlock.Code9 value={mantleCode("javascript")`10 const http = require('http');11 const ngrok = require("@ngrok/ngrok");12 const server = http.createServer((req, res) => {13 res.writeHead(200);14 res.end("Hello!");15 setTimeout(() => {16 Promise.resolve().then(() => {17 console.log("url:", server.tunnel.url());18 });19 }, timeout);20 });21 // Consumes authtoken from env automatically22 ngrok.listen(server).then(() => {23 console.log("url:", server.tunnel.url());24 });25 // really long line here that should wrap around and stuff Officia ipsum sint eu labore esse deserunt aliqua quis irure.26 `}27 />28 </CodeBlock.Body>29</CodeBlock.Root>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 context is provided in the surrounding content or the code block is self-explanatory eg. "In your index.js file, paste the following:".
const http = require('http');const ngrok = require("@ngrok/ngrok");const server = http.createServer((req, res) => { res.writeHead(200); res.end("Hello!");});// Consumes authtoken from env automaticallyngrok.listen(server).then(() => { console.log("url:", server.tunnel.url());});1<CodeBlock.Root>2 <CodeBlock.Body>3 <CodeBlock.Code4 value={mantleCode("javascript")`5 const http = require('http');6 const ngrok = require("@ngrok/ngrok");7 const server = http.createServer((req, res) => {8 res.writeHead(200);9 res.end("Hello!");10 });11 ngrok.listen(server).then(() => {12 console.log("url:", server.tunnel.url());13 });14 `}15 />16 </CodeBlock.Body>17</CodeBlock.Root>This example is included to show the interaction between the copy button and horizontal scrolling on a single verbose terminal command.
ffmpeg -i multichannel.mxf -map 0:v:0 -map 0:a:0 -map 0:a:0 -c:a:0 ac3 -b:a:0 640k -ac:a:1 2 -c:a:1 aac -b:2 128k out.mp41<CodeBlock.Root>2 <CodeBlock.Body>3 <CodeBlock.CopyButton />4 <CodeBlock.Code5 value={mantleCode(6 "bash",7 )`ffmpeg -i multichannel.mxf -map 0:v:0 -map 0:a:0 -map 0:a:0 -c:a:0 ac3 -b:a:0 640k -ac:a:1 2 -c:a:1 aac -b:2 128k out.mp4`}8 />9 </CodeBlock.Body>10</CodeBlock.Root>The CodeBlock supports server-rendered syntax highlighting for dynamic or user-provided 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.
1import {2 CodeBlock,3 createMantleCodeBlockValue,4 type MantleCodeBlockValue,5} from "@ngrok/mantle/code-block";6 7// Fetch highlighted HTML from your server8const response = await fetch("/api/shiki-highlight", {9 method: "POST",10 headers: { "Content-Type": "application/json" },11 body: JSON.stringify({ code, language: "typescript", showLineNumbers: true }),12});13const data = await response.json();14 15// Create a MantleCodeBlockValue from the server response16const value: MantleCodeBlockValue = createMantleCodeBlockValue({17 code: data.code,18 language: "typescript",19 preHtml: data.html,20 showLineNumbers: data.showLineNumbers,21 highlightLines: data.highlightLines,22 lineNumberStart: data.lineNumberStart,23});24 25<CodeBlock.Root>26 <CodeBlock.Body>27 <CodeBlock.CopyButton />28 <CodeBlock.Code value={value} />29 </CodeBlock.Body>30</CodeBlock.Root>;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 just calls the API with fetch.
Create a highlight server using createMantleServerSyntaxHighlighter:
1// highlight-service.ts — deploy as a sidecar alongside your Go service2import { createServer } from "node:http";3import { createMantleServerSyntaxHighlighter } from "@ngrok/mantle-server-syntax-highlighter";4 5const highlighter = createMantleServerSyntaxHighlighter();6 7createServer(async (req, res) => {8 // Handle CORS9 res.setHeader("Access-Control-Allow-Origin", "*");10 res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");11 res.setHeader("Access-Control-Allow-Headers", "Content-Type");12 if (req.method === "OPTIONS") {13 res.writeHead(204).end();14 return;15 }16 17 const chunks: Buffer[] = [];18 for await (const chunk of req) chunks.push(chunk);19 const { code, language, showLineNumbers, highlightLines, lineNumberStart } = JSON.parse(20 Buffer.concat(chunks).toString(),21 );22 23 const result = await highlighter.highlight({24 code,25 language,26 showLineNumbers,27 highlightLines,28 lineNumberStart,29 });30 res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(result));31}).listen(4444, () => console.log("Highlight server on :4444"));Then use it from any frontend the same way as the server-rendered example above, pointing at the highlight service URL:
1const response = await fetch("http://localhost:4444", {2 method: "POST",3 headers: { "Content-Type": "application/json" },4 body: JSON.stringify({ code, language: "json", showLineNumbers: true }),5});6const data = await response.json();7 8const value = createMantleCodeBlockValue({9 code: data.code,10 language: data.language,11 preHtml: data.html,12 showLineNumbers: data.showLineNumbers,13});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.
on_http_request: - expressions: # conditions are CEL expressions, see https://cel.dev - req.url.path.startsWith('/api') actions: - type: forward-internal config: url: https://api.internal # route dynamically based on a header using CEL interpolation - actions: - type: forward-internal config: url: "https://${req.headers('X-Custom-Header')}.internal"1import { CodeBlock, mantleCode } from "@ngrok/mantle/code-block";2 3const policyYml = mantleCode("yaml")`…`;4const policyJson = mantleCode("json")`…`;5 6<CodeBlock.Root defaultTab="yml">7 <CodeBlock.Header>8 <CodeBlock.TabList>9 <CodeBlock.TabTrigger value="yml">policy.yml</CodeBlock.TabTrigger>10 <CodeBlock.TabTrigger value="json">policy.json</CodeBlock.TabTrigger>11 </CodeBlock.TabList>12 </CodeBlock.Header>13 <CodeBlock.Body>14 <CodeBlock.CopyButton />15 <CodeBlock.TabContent value="yml">16 <CodeBlock.Code value={policyYml} />17 </CodeBlock.TabContent>18 <CodeBlock.TabContent value="json">19 <CodeBlock.Code value={policyJson} />20 </CodeBlock.TabContent>21 </CodeBlock.Body>22</CodeBlock.Root>;By default, the code block code will detect the preferred (or required) indentation of the given language. This is important for languages that require a certain indentation style; for example, Python and YAML are space-indented languages, so the code block will use spaces for indentation. This is done to ensure that the code is displayed and copied correctly and is easy to read. However, you can override this by passing indentation in the mantleCode() options.
# yaml indentation MUST use spaces (we infer this for you)on_http_request: actions: type: custom-response config: status_code: 200 content: Hello, World!// by default, mantle decides that javascript uses tabs,// but this example uses spaces for indentationconst http = require('http');const ngrok = require("@ngrok/ngrok");const server = http.createServer((req, res) => { res.writeHead(200); res.end("Hello!"); setTimeout(() => { Promise.resolve().then(() => { console.log("url:", server.tunnel.url()); }); }, timeout);});// Consumes authtoken from env automaticallyngrok.listen(server).then(() => { console.log("url:", server.tunnel.url());});The CodeBlock renders and applies syntax highlighting to blocks of code and is composed of several sub-components.
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:
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:
The CodeBlock content. This is where the code is rendered with pre-highlighted Shiki HTML.
All props from standard HTML pre attributes, plus:
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:
The (optional) title of a CodeBlock. Default renders as an h3 element; use asChild to render something else.
All props from standard HTML h3 attributes, plus:
The (optional) copy button of the CodeBlock. Render this as a child of the CodeBlock.Body to allow users to copy the code block contents to their clipboard.
All props from standard HTML button attributes, plus:
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.
All props from standard HTML button attributes, plus:
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:
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. Tab state is managed by CodeBlock.Root via defaultTab / activeTab / onActiveTabChange.
All props from standard HTML div attributes.
A pill-styled tab trigger for the CodeBlock header. Must be rendered within a CodeBlock.TabList.
All props from standard HTML button attributes, plus:
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: