Zach’s ugly mug (his face) Zach Leat­herman

Wrapper Elements around Code Blocks in Markdown

October 20, 2025

I encountered a surprising (to me) edge-case bug with Markdown code blocks in the markdown-it plugin today that I thought was worth sharing!

Consider this sample index.md template with a code block with an empty line (typical use in a markdown file):

```js
// Line 1

// Line 3
```

This renders as (which is fine, good, expected ✅):

<pre><code>// line 1

// line 3
</code></pre>

Now consider this Markdown code (HTML in a .md file). This resembles code that any generic syntax highlighting plugin (say, even the Eleventy one) would return (especially when preprocessing Markdown as Liquid first, as Eleventy projects do by default):

<pre><code>
// line 1

// line 3
</code></pre>

This above renders as-is (passthrough) without alteration (which is also fine, good, expected ✅).

Now consider this third case, in which your <pre> is wrapped in another HTML element (a <div> here for simplicity):

<div><pre><code>
// line 1

// line 3
</code></pre></div>

This renders as (broken, wrong, not expected ⛔️):

<div><pre><code>
// line 1
<p>// line 3
</code></pre></div></p>

(The same behavior happens with or without the <code> element)

The takeaway here is that if you’re using a wrapper element around your <pre> to nest some more sophisticated elements (for example, a Copy to Clipboard button for your code block), you need to make sure to return the triple backtick syntax instead of <pre> so that the Markdown parser won’t introduce unwanted paragraph elements.

Workarounds

It’s unlikely that most folks will run into this bug, but if you do decide you want a wrapper element around your code blocks there are a few options in the Eleventy Syntax Highlighting plugin.

You could use lineSeparator: "<br>" option instead of the default "\n" (though this is not recommended). This has the hefty caveat that it pollutes the textContent value of the node with extra line breaks (which would show in the output of, say, a Copy to Clipboard button).

Alternatively, you could wire up your own paired shortcode (or filter too, but that’s an exercise left to the reader) that works around the issue by deferring to the Markdown syntax highlighter:

import { pairedShortcode } from "@11ty/eleventy-plugin-syntaxhighlight";

const TRIPLE_TICK = "```";
const HIGHLIGHT_OPTIONS = {
	lineSeparator: "\n",
};

export default function(eleventyConfig) {
	// Our highlight shortcode
	eleventyConfig.addPairedShortcode("highlight", function(code, language) {
		if(this.page.inputPath.endsWith(".md")) {
			return `<div>\n\n${TRIPLE_TICK}${language || ""}
${code}
${TRIPLE_TICK}\n\n</div>`;
		}

		return `<div>${pairedShortcode(code, language, "", HIGHLIGHT_OPTIONS)}</div>`;
	});

	// This wires up Markdown’s triple-tick syntax
	eleventyConfig.amendLibrary("md", (mdLib) => {
		mdLib.set({
			highlight: function(code, language) {
				return pairedShortcode(code, language, "", HIGHLIGHT_OPTIONS);
			}
		})
	});
};

And here’s how you could use it in Nunjucks, Liquid, or Markdown (preprocessed by Nunjucks/Liquid):

{% highlight "js" %}
// line 1

// line 3
{% endhighlight %}

We’ll likely include this fix with the default highlight paired shortcode in a future version of the Eleventy Syntax Highlighting plugin. Follow along on GitHub Issue #77.

Update October 22, 2025: Just a small update to this approach to say that if you’re using an Eleventy Layout file that is Markdown, there is an additional issue to watch on Eleventy core.

Zach Leatherman is a builder for the web at Font Awesome and the creator of Build Awesome (née Eleventy/11ty), an award-winning open source website generator. He measures website performance with speedlify and at one point became too fixated on web fonts. He has given 86 talks in nine different countries at events like Beyond Tellerrand, State of the Browser, Smashing Conference, Jamstack Conf, CSSConf, and The White House. Learn more about Zach »