---
title: Highlight a code block
description: Write the fence line that highlights a code sample at build, captions it with a file name, and emphasizes chosen lines, with the bundled languages.
slug: /guides/code-blocks
order: 2
visibility: public
redirects_from: [/authoring/code-blocks]
---

# Highlight a code block

Prompt:

> Highlight the code samples on this page and mark the two lines I changed.

Also works: "Put the file name above each code sample." "The TypeScript on the setup page is rendering as plain text."

## What your tool does

- Reads each fenced block on the page and writes the language word after the opening backticks where it is missing, choosing a name from the bundled set.
- Adds `title="<file name>"` to the fence line where a caption is wanted, and a line set in braces, such as `{2,5-6}`, where lines are to be emphasized.
- Changes nothing inside the block: the emphasis and the caption are written on the fence line, so the copied text stays equal to the source.
- Runs `tzdocs check --source docs`, corrects a fence the check reports, and builds.
- Asks nothing.

## What you need

- For each code sample, which programming language it is (the guide lists the supported ones), or that you want it left plain.

## Before your AI starts

This section is for your AI coding tool: what it checks and gathers before it begins. You don't need to do these steps yourself.

- A page with fenced code blocks, and the tool installed ([Getting started](/getting-started/)).

## Steps

### 1. Write the fence line

A fenced code block is highlighted when the site builds. The page ships no highlighter script: the tokens are computed once, at build, and written into the HTML as spans. The text after the opening backticks is the info string: the language word, then optionally a title and a line set, in either order.

````markdown
```ts title="src/model.ts" {2,5-6}
export function readModel(source: string): Model {
  const pages = walk(source);
  /* a comment
     over two lines */
  const refusals = collide(pages);
  return { pages, refusals };
}
```
````

That fence renders as:

```ts title="src/model.ts" {2,5-6}
export function readModel(source: string): Model {
  const pages = walk(source);
  /* a comment
     over two lines */
  const refusals = collide(pages);
  return { pages, refusals };
}
```

The language word alone is enough for highlighting. The title and the line set are optional.

### 2. Choose the language word

The language name ends at the first whitespace character or opening brace, so `ts{1}` selects TypeScript and emphasizes line 1. A name may contain letters, digits, and the five characters `_ + # . -`. An info string beginning with `title=` or `{` has no language name: `title="notes.txt"` captions an unhighlighted block. A name with other characters is a check finding.

The highlighter is bundled with its common language set, plus Dockerfile and PowerShell. Each name is followed by the aliases that select the same language:

| Name | Aliases |
| --- | --- |
| bash | sh, zsh |
| c | h |
| cpp | cc, c++, h++, hpp, hh, hxx, cxx |
| csharp | cs, c# |
| css | — |
| diff | patch |
| dockerfile | docker |
| go | golang |
| graphql | gql |
| ini | toml |
| java | jsp |
| javascript | js, jsx, mjs, cjs |
| json | jsonc, json5 |
| kotlin | kt, kts, ktm, ktx |
| less | — |
| lua | pluto |
| makefile | mk, mak, make |
| markdown | md, mkdown, mkd |
| objectivec | mm, objc, obj-c, obj-c++, objective-c++ |
| perl | pl, pm |
| php | — |
| php-template | — |
| plaintext | text, txt |
| powershell | pwsh, ps, ps1 |
| python | py, gyp, ipython |
| python-repl | pycon |
| r | — |
| ruby | rb, gemspec, podspec, thor, irb |
| rust | rs |
| scss | — |
| shell | console, shellsession |
| sql | — |
| swift | — |
| typescript | ts, tsx, mts, cts |
| vbnet | vb |
| wasm | — |
| xml | html, xhtml, rss, atom, xjb, xsd, xsl, plist, wsf, svg |
| yaml | yml |

`text` and `plaintext` render the block without tokens. The figure still has the `data-language` attribute and the language class, which a block with no language word lacks.

A name outside the set, such as `elixir`, renders plain without a finding. A misspelled name therefore produces plain text without a warning, so inspect the rendered block. HCL and Terraform, HTTP, nginx, Vue, and Svelte are outside the set.

The name `mermaid` marks a diagram, which the build draws where Mermaid is installed and otherwise shows as a plain block. [Add callouts and diagrams](/guides/callouts-and-diagrams/) is the guide.

### 3. Caption the block

`title="src/model.ts"` captions the block, and single quotes work too: `title='src/model.ts'`. A title cannot contain the quote character around it, so use the other kind of quote. A backslash does not help: the Markdown parser removes a backslash before punctuation, so `\"` arrives as a plain `"`. The title is written into the page escaped and never contains markup.

### 4. Emphasize lines

`{2,5-6}` emphasizes lines 2, 5, and 6 of the block, counted from 1. The entries are numbers or ascending ranges `a-b`, separated by commas. Whitespace inside the braces is allowed, and a line listed twice is emphasized once. An entry that is not a number or a range is reported as not parsing, and a zero or a reversed range is reported as not a positive ascending range. A line past the block's last line is reported too, and the lines that exist are still emphasized.

Mark lines on the fence line, not with comments in the code. The [copy control](/concepts/glossary/#copy-control) copies the block as authored, so the copied text stays equal to the source. Writing `\{1\}` does not turn the line set off: the parser removes the backslashes before the renderer sees the string.

### 5. Check the fences

An unknown key, an unquoted title, or syntax from another tool, such as `hl_lines="2 3"`, is a metadata finding. Parsing stops at the unrecognized text, and a title or line set parsed before that point stays active: `ts title="Example" {1} invalid` keeps the caption and the emphasis. The check reports the file, the fence's line, and the info string as written. For example, the check reports the fence below.

````markdown
```ts hl_lines="2"
let x = 1
```
````

The finding line it prints has this form.

```text
code block in guide.md — line 41, fence "ts hl_lines=\"2\"": the metadata "hl_lines=\"2\"" does not parse
```

[Code fences](/reference/refusals/#code-fences) says which line a finding gives for a block inside a list or a block quote.

## Expected result

Every block becomes the same figure whatever its metadata, shown below with ellipses marking the lines' content.

```html
<figure class="docs-code" data-language="ts">
<figcaption>src/model.ts</figcaption>
<pre><code class="language-ts"><span class="docs-line">…
</span><span class="docs-line docs-line-hl">…
</span></code></pre>
</figure>
```

Each token is a span with one of the highlighter's class names: `hljs-keyword`, `hljs-string`, `hljs-comment`, and the rest. Each line is a span classed `docs-line`, and an emphasized line also `docs-line-hl`. A token that spans lines, such as a block comment, is closed at each newline and reopened on the next line, so each line's span is complete. Without a title there is no caption. Without a language there is no `data-language` attribute and no language class.

The spans add no characters. The copy control at the block's right copies the block's text plus one trailing newline. It copies the whole text even where the display shortens a long line with an ellipsis. [Output](/reference/output/#the-styling-surface) lists the token colors, and [Style the site](/guides/style-the-site/) explains how a site redefines them.

## Refusals

Each is an advisory finding: the check exits with code 1, and the build proceeds, rendering what parsed. [Refusals and findings](/reference/refusals/) gives the exact text.

| Finding | Cause | Remedy |
| --- | --- | --- |
| the language word is not a name | The name has a character outside letters, digits, and `_ + # . -`. | Use a name from the bundled set, or leave the language out. |
| the title is given twice | Two `title=` entries on one fence. | Keep one. |
| the line set entry does not parse | An entry in the braces is not a number or a range `a-b`, a negative number included. | Write numbers and ascending ranges, separated by commas. |
| the line set entry is not a positive ascending range | A zero, or a reversed range. | Count from 1 and write the smaller number first. |
| the line set gives a line past the block's last line | The set lists a line the block does not have. | Remove the entry or shorten the range. |
| the metadata does not parse | Text after the language word that is neither a title nor a line set. | Remove it, or rewrite it in this page's grammar. |

## Related

- [Output](/reference/output/#the-styling-surface) lists the token properties, the classes, and the selectors the figure uses.
- [Style the site](/guides/style-the-site/) explains how to redefine the palette with a stylesheet or a shell.
- [Add a page](/guides/add-a-page/) covers the page around the block.
