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

Wrapper Elements around Code Blocks in Markdown

October 20, 2025 #218700

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.


Older >
StarCraft II is still great.

Zach Leatherman IndieWeb Avatar for https://zachleat.com/is a builder for the web at Font Awesome and the creator/maintainer of IndieWeb Avatar for https://www.11ty.devEleventy (11ty), an award-winning open source site generator. At one point he became entirely too fixated on web fonts. He has given 86 talks in nine different countries at events like Beyond Tellerrand, Smashing Conference, Jamstack Conf, CSSConf, and The White House. Formerly part of CloudCannon, Netlify, Filament Group, NEJS CONF, and NebraskaJS. Learn more about Zach »

Shamelessly plug your related post

These are webmentions via the IndieWeb and webmention.io.

Sharing on social media?

This is what will show up when you share this post on Social Media:

How did you do this? I automated my Open Graph images. (Peer behind the curtain at the test page)