Using Markdown with Eleventy
Recommendations for writing Markdown documents and extending Eleventy’s built-in Markdown support by advanced configuration and plugins.
Contents
- Introduction
- How to Write Text in Markdown
- Adding Comments to a Markdown Document
- Automatic Figures for Fenced Code Blocks
- Preserve Text Formatting in TOC Entries
- Custom Containers in Markdown with Caption
- Mixing Markdown and HTML
Introduction
Eleventy provides native, first-class support for Markdown. By default, it utilizes the markdown-it library, which supports extensibility via plugins to enhance functionality.
How to Write Text in Markdown
Thin spaces can be entered as HTML character entity   and non-breaking spaces as to make them distinguishable from normal spaces in text editors. The same applies to − to denote a minus sign (»−«) that can be easily confused with the en dash (»–«).
Adding Comments to a Markdown Document
While native Markdown lacks built-in comment syntax, invalid hyperlink markup serves as a workaround to omit text. Content enclosed within these specific character sequences is ignored by markdown-it and most other Markdown renderers:
This is a paragraph.
[//]: # (This is a comment.)
This is a paragraph.
[This is a comment.]: #
This is a paragraph.When utilizing Liquid within Eleventy, specific sections of a Markdown document can be enclosed in {% comment %}…{% endcomment %} templates to exclude them from the final output.
Since Markdown supports inline HTML, standard HTML comments (<!-- Comment -->) are also valid. However, depending on the specific Markdown library, these comments may still be included in the rendered HTML output.
Automatic Figures for Fenced Code Blocks
The markdown-it fenced code renderer can be modified to enclose the rendered pre element within a figure element:
import markdownIt from 'markdown-it';
export default function (eleventyConfig) {
…
const mdSetup = markdownIt('commonmark', { html: true });
const proxy = (tokens, idx, options, env, self) =>
self.renderToken(tokens, idx, options)
;
const defaultFenceRenderer = mdSetup.renderer.rules.fence || proxy;
mdSetup.renderer.rules.fence = (tokens, idx, options, env, self) => {
return `<figure class="listing">
${defaultFenceRenderer(tokens, idx, options, env, self)}
</figure>\n`;
};
…
}Preserve Text Formatting in TOC Entries
By default, the markdown-it plugin markdown-it-table-of-contents strips Markdown formatting from table of contents entries. Text formatting can be preserved by providing a custom implementation for getTokensText to render the tokens of each entry:
import markdownIt from 'markdown-it';
import markdownItAnchor from 'markdown-it-anchor';
import markdownItTOC from 'markdown-it-table-of-contents';
export default function (eleventyConfig) {
…
eleventyConfig.amendLibrary('md', (mdLib) => mdLib.use(markdownItAnchor));
eleventyConfig.amendLibrary('md', (mdLib) =>
mdLib.use(
markdownItTOC,
{
includeLevel: [2, 3],
transformContainerOpen: () => {
return '<h2 id="toc"><a href="#toc">Table of Contents</a></h2>';
},
transformContainerClose: () => { return ''; },
// Allow markdown formatting in TOC entries. The default
// implementation of `getTokensText` removes any formatting.
getTokensText: (tokens) => { return mdLib.renderer.render(tokens); },
}
)
);
…
}Custom Containers in Markdown with Caption
The markdown-it plugin @mdit/plugin-container enables the generation of custom containers, including figures, listings, and blockquotes. The example below demonstrates the creation of a source code listing that includes a caption:
This is a paragraph.
:::listing This is a _very_ important code sample.
```
int main()
{
return 42;
}
```
:::
This is a paragraph.This markup is rendered to the following HTML:
<p>This is a paragraph.</p>
<figure class="listing">
<pre><code>int main()
{
return 42;
}</code></pre>
<figcaption>This is a <em>very</em> important code sample.</figcaption>
</figure>
<p>This is a paragraph.</p>The implementation renders figure elements containing a figcaption element at the bottom. However, the close renderer lacks access to the caption data available during the initial open renderer phase. Furthermore, because custom containers can be nested, the caption must be pushed onto a stack during open rendering and subsequently popped during close rendering:
import markdownIt from 'markdown-it';
import { container } from '@mdit/plugin-container';
export default function (eleventyConfig) {
…
let figureCaptions = [];
const figureOpenRender = (tokens, index, _options) => {
const info = tokens[index].info.trim();
const separatorPos = info.indexOf(' ');
let figureType = '', figureCaption = '';
if (separatorPos !== -1) {
figureType = info.substring(0, separatorPos).trim();
figureCaption = info.substring(separatorPos + 1).trim();
} else {
figureType = info.trim();
}
figureCaptions.push(figureCaption);
return `<figure class="${figureType}">`;
};
const figureCloseRender = (tokens, index, _options) => {
const figureCaption = figureCaptions.pop();
const figcaption =
figureCaption ?
`<figcaption>${mdSetup.renderInline(figureCaption)}</figcaption>` : ''
;
return `${figcaption}</figure>`;
};
eleventyConfig.amendLibrary('md', (mdLib) => mdLib.use(container, {
name: 'listing',
openRender: figureOpenRender,
closeRender: figureCloseRender,
}));
eleventyConfig.amendLibrary('md', (mdLib) => mdLib.use(container, {
name: 'figure',
openRender: figureOpenRender,
closeRender: figureCloseRender,
}));
…
}Mixing Markdown and HTML
Markdown capabilities regarding containers and nesting are limited. For example, certain Markdown extensions for definition lists do not support nesting paragraphs within definitions, an operation that is straightforward in HTML:
<dl>
<dt>Term 1</dt>
<dd><p>Definition of Term 1.</p></dd>
<dt>Term 2</dt>
<dd><p>Definition of Term 2.</p></dd>
</dl>Markdown extensions for definition lists typically support syntax that allows paragraphs to be nested within the definitions:
Paragraph before the definition list.
Term 1
: Definition of Term 1.
Term 2
: Definition of Term 2.
Paragraph after the definition list.Certain Markdown extensions for definition lists, such as markdown-it-deflist, do not support generating paragraphs within definitions. While this issue was resolved in @mdit/plugin-dl via a specific bug fix, using inline HTML within the Markdown document remains a reliable workaround for unsupported engines:
Paragraph before the definition list.
Term 1
: <p>The definition of Term 1 is _very_ short.</p>
Term 2
: <p>Definition of Term 2.</p>
Paragraph after the definition list.When rendering the Markdown block above into HTML, the paragraphs nested within the definition elements process correctly. However, Markdown markup contained inside the HTML p element is ignored because Markdown rules prevent the rendering of inline Markdown inside HTML block elements. The Eleventy Render plugin, which is included by default, provides a mechanism to pass the content of the paragraph directly to the Markdown renderer:
The plugin can be added to the Eleventy configuration file as follows:
import { RenderPlugin } from '@11ty/eleventy';
export default function (eleventyConfig) {
eleventyConfig.addPlugin(RenderPlugin);
}Note that the HTML <p>…</p> markup must be omitted because the Markdown renderer automatically encloses the rendered output within a p element:
Term 1
: {% renderTemplate "md" %}
The definition of Term 1 is _very_ short.
{% endrenderTemplate %}
Term 2
: <p>Definition of Term 2.</p>The Markdown block above renders into the following HTML:
<dl>
<dt>Term 1</dt>
<dd><p>The definition list of Term 1 is <em>very</em> short.</p></dd>
<dt>Term 2</dt>
<dd><p>Definition of Term 2.</p></dd>
</dl>To render only inline markup into HTML without enclosing it within a block element, a paired shortcode that invokes an inline Markdown renderer can be utilized.