Code blocks and Code Groups

Add filenames, exact Copy behavior, wrapping, collapse, and shareable groups to Hugo code examples.

OINK enhances Hugo’s ordinary fenced code blocks without replacing Chroma or requiring a browser highlighter. The server emits the complete code and shell; small page-scoped scripts only enable Copy, visual collapse, and tab state.

Enhanced fences

Add metadata in Hugo’s fence attribute list. A fence without attributes still receives the same responsive shell and its normal Copy default. filename adds a visible header; title is its compatible alias, and setting both is a build error. With neither, OINK uses a compact overlay instead of an empty title row.

Authoring

content/docs/example.md
MARKDOWN
```yaml {filename="hugo.yml" copy="all" lineNos="table" hl_lines="4 7-9" wrap=false collapse=18}
params:
  offlineSearch: true
```

Live result

Rendered result

This block combines a filename, inline line numbers, a stable root ID, line links, and highlighted source lines. Line numbers begin at 12, while hl_lines still addresses the source lines inside the fence:

hugo.yaml
YAML
12markup:
13  highlight:
14    noClasses: false
15params:
16  offlineSearch: true
17  ui:
18    sidebar_menu_foldable: true

Shell parameters

Attribute Values Behavior
filename string Visible filename and accessible group name
title string Alias for filename on an ordinary fence
copy all, command, false, or true Copy policy; true is shorthand for all
wrap true or false Visually wrap long lines without changing text
collapse positive integer Initial maximum number of visible source lines
label string Accessible label when no filename is suitable
id string Stable public block ID and line-anchor prefix

Hugo generic class, safe data-*, aria-*, and global attributes remain on the .td-code root. Names beginning with data-td-code and data-language are reserved. OINK rejects event-handler and inline-style attributes. Use label to override a filename-derived accessible name; a generic aria-label together with label or filename is a build error.

Hugo options

The render hook continues to pass these options to Hugo:

  • lineNos, lineNoStart, and anchorLineNos;
  • hl_lines;
  • tabWidth and style.

Class-based Chroma markup remains inside .highlight and .chroma, so existing token-level overrides keep working. The new stable outer element is .td-code; sites using direct-child selectors such as .td-content > .highlight should update those selectors.

The visible language label normalizes the common bash, sh, and shell lexer aliases to BASH. The original lexer value is still passed to Chroma and retained in data-language.

Diffs deliberately use Chroma’s standard diff lexer rather than a custom transformer:

Authoring

content/docs/configuration.md
MARKDOWN
```diff {filename="hugo.yaml.diff"}
 params:
-  offlineSearch: false
+  offlineSearch: true
```

Rendered result

hugo.yaml.diff
DIFF
 params:
-  offlineSearch: false
+  offlineSearch: true

Copy semantics

Ordinary source defaults to copy="all". console and shell-session default to copy="command": only lines carrying Chroma prompt tokens are copied, and prompt/output tokens are excluded. Use copy="all" when a complete transcript is intentional. command on another language is a build error.

Copy preserves indentation, internal blank lines, and Unicode, removes line numbers, trims only trailing newline characters, and appends exactly one final newline. A session lexer that emits no prompt token reports a localized failure and copies nothing. Set params.disable_click2copy_chroma: true to hard-disable Copy for the entire site.

Copy is shown as a compact icon without adjacent text. Its localized label is still exposed to assistive technology and as a hover tooltip; success and failure also change the icon and update the live status message.

For a multi-line terminal command, include the continuation prompt (normally >) on every continued transcript line. Chroma classifies an unprompted line as output, so copy="command" deliberately excludes it.

The Copy action on this live session copies the two commands, not the prompts or output:

Authoring

content/docs/terminal.md
MARKDOWN
```console {title="Terminal session"}
$ hugo version
hugo v0.164.0+extended darwin/arm64
$ hugo --gc --minify
Total in 742 ms
```

Rendered result

Terminal session
CONSOLE
$ hugo version
hugo v0.164.0+extended darwin/arm64
$ hugo --gc --minify
Total in 742 ms

Wrapping and collapse

wrap=true changes presentation only; copied source is untouched. It is incompatible with Chroma’s table line-number layout because separately wrapped gutter and source cells would drift. Use inline line numbers or disable wrap. OINK fails the build instead of silently misaligning them.

collapse=N is progressive enhancement. The server always emits all source; the browser clips only after it can measure the Nth real Chroma line. Without JavaScript, in assistive technology, and in print, the listing remains complete. Reduced-motion preferences disable the height animation.

The first example wraps a long value without altering copied text:

Authoring

content/docs/downloads.md
MARKDOWN
```text {filename="config/artifacts.env" wrap=true}
ARTIFACT_URL=https://downloads.example.com/releases/2026/08/oink-complete-offline-distribution-arm64.tar.zst
CHECKSUM=sha256:6d3dce4f7acb18f586469adcb80ab35f3e859f9837786e151cfbc2b3c0f587b2
```

Rendered result

config/artifacts.env
TEXT
ARTIFACT_URL=https://downloads.example.com/releases/2026/08/oink-complete-offline-distribution-arm64.tar.zst
CHECKSUM=sha256:6d3dce4f7acb18f586469adcb80ab35f3e859f9837786e151cfbc2b3c0f587b2

The second emits all lines on the server but initially shows six in a browser:

Authoring

content/docs/configuration.md
MARKDOWN
```yaml {filename="hugo.yaml" collapse=6}
baseURL: https://docs.example.com/
title: Product Documentation
defaultContentLanguage: en
languages:
  en:
    label: English
    weight: 1
  zh:
    label: 简体中文
    weight: 2
params:
  offlineSearch: true
```

Rendered result

hugo.yaml
YAML
baseURL: https://docs.example.com/
title: Product Documentation
defaultContentLanguage: en
languages:
  en:
    label: English
    weight: 1
  zh:
    label: 简体中文
    weight: 2
params:
  offlineSearch: true

Set a page-unique explicit id when publishing line-number links. IDs cannot contain ASCII whitespace or control characters and cannot collide with another code component’s generated viewport, tab, panel, title, or line-anchor ID. OINK reports any such collision as a build error:

Authoring

content/docs/server.md
MARKDOWN
```go {id="server-start" lineNos="inline" anchorLineNos=true}
func start() {}
```

Rendered result

GO
1func start() {}

OINK derives unique line-anchor prefixes from that ID. Generated IDs are safe inside a page but depend on the block ordinal and are not a permalink contract; inserting an earlier fence can change them.

Code Groups

Use code-group when examples are alternatives rather than independent tabs:

Authoring

content/docs/install.md
GO-HTML-TEMPLATE
{{< code-group id="docs-install-client" sync="docs-package-manager" persist=false
    label="Choose a package manager" copy="all" >}}
  {{< code-tab title="npm" value="npm" lang="bash" >}}
npm install @example/client
  {{< /code-tab >}}
  {{< code-tab title="pnpm" value="pnpm" lang="bash" selected=true >}}
pnpm add @example/client
  {{< /code-tab >}}
  {{< code-tab title="yarn" value="yarn" lang="bash" >}}
yarn add @example/client
  {{< /code-tab >}}
{{< /code-group >}}

Rendered result

BASH
npm
npm install @example/client
pnpm
pnpm add @example/client
yarn
yarn add @example/client

code-tab contains raw code, not Markdown. OINK removes the framing newline and closing-shortcode indentation while preserving all source whitespace inside. Because a Markdown formatter can otherwise reflow that raw body, put <!-- prettier-ignore --> immediately before each live code-group when using Prettier, as in the examples below.

Group and tab parameters

Every group requires a page-unique lower-case id. Optional sync, persist, label, copy, wrap, and collapse values apply to the group; the last three are inherited defaults. persist defaults to true.

Every child requires a plain-text title and stable lower-case value. lang defaults to text; selected, copy, wrap, collapse, and the Hugo highlight options can override group defaults. A group cannot be empty, repeat a value, or contain more than one selected=true child. Filenames are omitted inside groups because the tab itself identifies the example.

Selection, sync, and persistence

A selected panel has the public hash #<group-id>-<value>, for example #install-client-pnpm. Initial selection priority is URL hash, saved value, selected=true, then the first child.

Groups sharing sync select the same value when that value exists in each group; a peer missing it stays unchanged. A user selection updates the hash with replaceState and saves the value when persistence is enabled. Visiting a shared hash activates the requested examples without overwriting the reader’s saved preference. persist=false disables storage, not in-page synchronization.

Live synchronized groups

The rendered install group above and the run group below share the same sync key. Choose a package manager in either group and the other follows. The first group’s npm, pnpm, and yarn panels also have shareable hashes.

Authoring

content/docs/run.md
GO-HTML-TEMPLATE
{{< code-group id="docs-run-client" sync="docs-package-manager" persist=false >}}
  {{< code-tab title="npm" value="npm" lang="bash" >}}
npm run docs:dev
  {{< /code-tab >}}
  {{< code-tab title="pnpm" value="pnpm" lang="bash" selected=true >}}
pnpm docs:dev
  {{< /code-tab >}}
  {{< code-tab title="yarn" value="yarn" lang="bash" >}}
yarn docs:dev
  {{< /code-tab >}}
{{< /code-group >}}

Rendered result

BASH
npm
npm run docs:dev
pnpm
pnpm docs:dev
yarn
yarn docs:dev

Output and compatibility

Print hides controls and tab rows, expands every listing, and places each group title before its code. Markdown output turns every grouped or legacy tab into a readable titled fence and chooses a longer delimiter when source contains backticks. Feeds and other non-interactive outputs use stacked examples. Pages without applicable code or tabs do not load their runtimes.

Existing tabpane source and its td-tp-persist:* browser keys remain compatible. Prism remains a legacy alternative and does not receive Enhanced Code Blocks or Code Groups. Specialized mermaid, math, chem, markmap, and plantuml hooks continue using their own renderers.