Markdown Code Blocks: Complete Guide with Syntax, Languages & Examples

Fenced code, language tags, diff, and filenames.

Page content

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.

sample wiki page with code block

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.

Subscribe

Get new posts on AI systems, Infrastructure, and AI engineering.