When an agent includes a fenced code block with an explicit language tag in its markdown response, the session view tokenises the content and paints keywords, strings, numbers, comments and other syntax classes with theme-aware colours. Plain prose in unlabelled blocks is left untouched.
The agent writes a standard fenced code block with a language name on the opening fence:
```typescript
interface User {
readonly id: string
readonly name: string
}
function greet(user: User): string {
return `Hello, ${user.name}!`
}
```
The markdown renderer pipes the block through rehype-highlight, which uses highlight.js to classify each token and wrap it in a <span class="hljs-*">. Those spans are then styled by a set of .acp-markdown .hljs-* rules that resolve their colour from theme-aware CSS custom properties, so the palette tracks the active light or dark theme automatically.
No server round-trip is involved — tokenisation runs in the browser as the message renders.
Highlight.js ships an auto-detector that will guess a language when no tag is given on the fence. In practice, for short snippets of prose it often misidentifies common English words like as, is, or with as language keywords, producing false-positive colouring on plain text.
To avoid this, auto-detection is turned off (detect: false). An unlabelled fence:
```
This is plain text. No syntax highlighting is applied.
```
renders as plain monospace text with no token spans. If you want highlighting, tag the fence with a language name.
Note on paste auto-fencing. When you paste multi-line content into the prompt input, a separate detector — the VS Code language-detection model, run server-side — does attempt to identify the language and wrap the paste in a tagged fence. That happens before the agent ever sees the text; by the time the message is rendered, the fence tag is already present and the rendering path described here runs with
detect: falseas usual. See Pasting Code.
Highlight.js recognises over 190 languages out of the box. Common ones you will encounter from agent output include:
html, css, scss, javascript, typescript, json, yaml, xmlc, cpp, rust, go, zigbash, shell, python, ruby, perl, lua, phpjava, kotlin, scala, groovy, csharp, fsharphaskell, elixir, erlang, ocaml, clojuresql, graphql, protobufdiffUse whichever language name or alias you normally would on GitHub fenced blocks — ts and typescript, py and python, sh and bash all work.
Unknown language names are silently ignored (via ignoreMissing: true). The block still renders — just without token colouring — so you will never see a "language not supported" error in place of your code.
Each syntax class resolves to a CSS custom property that is defined twice — once on :root for the light theme, once on [data-theme="dark"] for the dark theme:
| Syntax class | CSS variable | Typical tokens |
|---|---|---|
| Keyword | --hljs-keyword | if, return, function, class, const |
| String | --hljs-string | "...", '...', template strings |
| Number / literal | --hljs-number, --hljs-literal | 42, 3.14, true, false, null |
| Comment | --hljs-comment | // ..., # ..., /* ... */ |
| Function / title | --hljs-function, --hljs-title | Function names at definition and call sites |
| Built-in | --hljs-built-in | Array, console, Object, print |
| Variable | --hljs-variable | $var, @var, template variables |
| Attribute / property | --hljs-attr, --hljs-attribute | JSON keys, HTML attributes, YAML keys |
| Parameter | --hljs-params | Function parameter names |
| Type | --hljs-type | Type annotations, class titles |
| Tag | --hljs-tag | <div>, <span>, HTML element names |
| Operator | --hljs-operator | =, +, ->, =>, punctuation |
| Meta | --hljs-meta | Decorators, directives, shebangs |
| Symbol | --hljs-symbol | Ruby symbols, atoms, bullets |
| Regexp | --hljs-regexp | /foo/g literals |
| Link | --hljs-link | URLs in markdown/docstrings |
| Section heading | --hljs-section | Markdown headings, INI sections |
| Deletion / addition | --hljs-deletion, --hljs-addition | Diff - / + lines |
Keywords and function names are rendered bold; comments are italic; deletions and additions in diff blocks are coloured red and green respectively to match the diff viewer's conventions.
When the user toggles theme (via settings or OS preference), the token spans keep their class names but re-resolve the --hljs-* variables from the new theme's root, so the palette switches instantly with no re-render.
Every highlighted fenced block still shows the standard copy button in its top-right corner on hover. The text copied to the clipboard is the original source — no hljs-* spans or class names end up in the copied content.
Inline code spans (single backticks inside a paragraph — `like this`) are not tokenised. They continue to render in the app's inline-code style: small monospace text on a subtle background tint. Syntax highlighting only applies to fenced blocks.
Fenced blocks tagged mermaid, likec4, or c4 are not affected by syntax highlighting. They continue to route to the Mermaid and LikeC4 diagram renderers and display as interactive diagrams. When you toggle the Source view on a diagram block, the raw DSL currently renders as plain monospace text — syntax highlighting for Mermaid and LikeC4 grammars is not yet provided by highlight.js, so these sources are not tokenised.
The highlight pipeline runs as a rehype plugin on the same react-markdown tree that produces the rest of the message content. The relevant configuration lives in apps/braide/src/components/sessions/cards/copy-markdown.tsx:
const rehypePlugins: PluggableList = [
[rehypeHighlight, { detect: false, ignoreMissing: true }],
]
The CSS that maps hljs-* classes to the theme palette lives in apps/braide/src/app/globals.css, immediately after the code block copy-button rules and the mermaid theme tokens, so all markdown rendering styles are co-located.