Markdown Code Blocks: Complete Guide with Syntax, Languages & Examples
Fenced code, language tags, diff, and filenames.
Fenced code blocks carry multi-line source in Markdown. The token after the opening fence is a language tag, and only the renderer decides what that tag highlights.
This guide is part of Documentation Tools in 2026: Markdown, LaTeX, PDF & Printing Workflows.

GitHub looks the language tag up with Linguist and the keywords in languages.yml. Hugo 0.164 hands the same tag to Chroma. A tag Chroma does not know is still a code block, drawn as plain text. Headings, tables, and the other block types sit in the Markdown Cheatsheet.
Markdown Code Blocks Overview
Markdown has three ways to mark code. Inline code is a span inside a paragraph. An indented block and a fenced block are both multi-line, and only the fence carries a language tag.
| Type | Syntax | Highlighting | Notes |
|---|---|---|---|
| Inline code | `code` |
No | One span inside a sentence |
| Indented block | 4 spaces or 1 tab on each line | No | No info string, so no language tag |
| Fenced block | ```lang … ``` or ~~~ |
Yes, when the renderer knows lang |
The form GitHub documents |
An indented block cannot carry a language. CommonMark treats a line indented by four spaces as code and gives the highlighter nothing to match, which is why a fence is the form to use when the sample should be colored.
A fence is a line of at least three backticks or three tildes, the code, then a closing line of the same character at least as long. The closing line has no info string. To show a three-backtick fence inside the sample, open and close with four backticks. GitHub’s code-block docs ask for a blank line before and after the fence so the raw file is easier to read; the fence still parses without that blank line.
When the following source is stored as Markdown:
```python
def hello():
print("Hello, world!")
```
this Hugo site renders it as:
def hello():
print("Hello, world!")
Inline code uses the same backtick rule at a smaller scale. A pair of single backticks is enough for git status. When the span itself contains a backtick, use two backticks on each side:
`` `code` ``
Diff Syntax Highlighting
A fence tagged diff is how a change is shown as text. Hugo’s Chroma lexer accepts diff and udiff. GitHub highlights that same identifier from its language list.
- old line that will be removed
+ new line that will be added
unchanged line
Lines that start with - take the deletion style, and lines that start with + take the insertion style. A line with neither prefix is ordinary text in that lexer. The coloring is the whole line. A changed word in the middle of a line is not marked on its own.
Language Identifiers
The info string’s first word is the language. Hugo’s docs treat that word as case-insensitive. If Chroma has no lexer for it, Hugo 0.164 emits a plain <pre><code class="language-…"> with no highlight wrapper. A local render of notalang did that. guessSyntax defaults to false, and Hugo’s docs say only five lexers implement automatic detection, so an omitted tag stays plain text rather than a guessed language.
HTML produced from these fences uses the class language-python and the same token. That class is what a converter has to preserve when it turns a highlighted page back into Markdown, which is the mapping covered in Converting HTML to Markdown with Python.
The identifiers below are the ones listed for those languages in Hugo’s Chroma table. On a Hugo 0.164 render, cs, yml, and markdown were colored as well, even where the published table prints csharp, yaml, and md.
| Language | Hugo Chroma identifiers |
|---|---|
| Python | python, py |
| JavaScript | javascript, js |
| TypeScript | typescript, ts, tsx |
| Java | java |
| C | c |
| C++ | cpp, c++ |
| C# | csharp, c# |
| Go | go, golang |
| Ruby | ruby, rb |
| PHP | php |
| Rust | rust, rs |
| Swift | swift |
| Kotlin | kotlin |
| HTML | html |
| CSS | css |
| Shell | bash, sh, shell, zsh |
| SQL | sql |
| JSON | json |
| YAML | yaml |
| Markdown | md, mkd |
| Perl | perl |
| Lua | lua |
| R | r |
| Matlab | matlab |
| Makefile | make, makefile |
| Diff | diff, udiff |
GitHub Pages sites that still highlight through Jekyll need the identifier in lower case. GitHub’s own docs state that requirement separately from github.com rendering.
GitHub also treats a few fences as diagrams rather than colored source: mermaid, geojson, topojson, and stl. Hugo’s Chroma table does not list mermaid. On a Hugo site the fence stays source text until a render hook, shortcode, or script draws it. The Mermaid Diagrams Quickstart and Cheatsheet covers that setup.
Specifying a Filename
Nothing in CommonMark or in GitHub’s code-block docs turns the info string into a filename. Hugo’s highlighting options are brace pairs such as {linenos=inline}. They do not include a filename field.
Colon after the language
Some generators treat js:app.js as a language plus a name:
```js:app.js
console.log("Hello, world!");
```
GitHub does not. Hugo 0.164 keeps the whole token as the code element’s class (language-js:app.js) and does not print a filename. Which dialects add meaning past the first word is the comparison in GFM vs CommonMark vs Pandoc Markdown.
Name above the fence
A line of bold inline code above the fence shows the name on GitHub, on this site, and in other CommonMark renderers:
**`app.js`**
```js
console.log("Hello, world!");
```
A heading works the same way:
#### `app.js`
```js
console.log("Hello, world!");
```
Name inside the sample
A comment in the first line travels with the code when someone copies the block:
```js
// app.js
console.log("Hello, world!");
```
| Method | GitHub | Hugo 0.164 | Shows the name when copied |
|---|---|---|---|
js:app.js in the info string |
No filename | No filename; class is the whole token | No |
| Bold code or a heading above the fence | Yes | Yes | No |
| Comment on the first line | Yes | Yes | Yes |
Escaping Backticks
Four backticks on the outside display a three-backtick fence, which is the pattern for a page that documents Markdown:
````markdown
```python
# This triple-backtick fence is inside a four-backtick fence
print("hello")
```
````
The closing fence has to be the same character as the opening one, and at least as long. A shorter closer ends the block early and leaves the rest of the sample as ordinary paragraphs.
Hugo Highlight Options
This site’s config.toml has no [markup.highlight] block, so Hugo’s defaults apply: fenced blocks are highlighted, the style is monokai, styles are written inline (noClasses true), and line numbers are off. Hugo 0.164 still colors a fence that asks for nothing but a language, as the Python sample above does.
Line numbers and emphasized lines are brace options after the language. This fence asks for inline numbers and marks line 2:
```python {linenos=inline hl_lines=[2]}
def hello():
print("Hello, world!")
```
1def hello():
2 print("Hello, world!")
linenos accepts inline, table, true, or false. hl_lines is a list of line numbers or ranges. linenostart sets the first displayed number. The same keys are case-insensitive. The highlight shortcode takes them as one quoted string, and that string still rendered line numbers on Hugo 0.164:
{{< highlight python "linenos=true,hl_lines=2" >}}
def hello():
print("Hello, world!")
{{< /highlight >}}
To move the colors into a stylesheet, set noClasses to false and generate the CSS. Hugo 0.164 accepts --mode and --modeSelector on that command:
hugo gen chromastyles --style=monokai --mode=light > assets/css/highlight.css
hugo gen chromastyles --style=monokai --mode=dark --modeSelector > assets/css/highlight-dark.css
--modeSelector scopes each rule under a top-level class such as .dark .chroma. The flag list is hugo gen chromastyles --help.
When the Fence Looks Wrong
Check the generated element before changing the sample. On this Hugo version a known language is a div.highlight whose code element has data-lang set to the identifier. An unknown identifier is a bare pre and code with class="language-…", which is what notalang produced.
If the name app.js is missing and the class is language-js:app.js, the colon form was used. Move the name to the line above the fence.
If a tutorial’s own fences appear as loose paragraphs, the outer fence is the same length as the inner one. Lengthen the outer fence.
If a short command swallows the backtick in the middle of a sentence, the span needs a longer backtick pair than the character it contains.