Using Markdown with Eleventy
Eleventy provides first-class support for the Markdown format. By default, it uses the markdown-it Markdown library, which can be extended by plugins for enhanced functionality.
Contents
- How To Write Markdown Code
- Automatic Figures for Fenced Code Blocks
- Preserve Text Formatting in TOC Entries
- Custom Containers in Markdown with Caption
- Mixing Markdown and HTML
How To Write Markdown Code
Markdown does not support commenting out lines and adding comments. However, lines surrounded by a certain character sequence consisting of invalid hyperlink markup will not be rendered by markdown-it and likely other Markdown renderers:
This is a paragraph. [//]: # (This is a comment.) This is a paragraph. [This is a comment.]: # This is a paragraph.When using Liquid in Eleventy, parts of the Markdown document can be enclosed in
{% comment %}…{% endcomment %}to prevent them from being included in the output.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 (»–«).
Automatic Figures for Fenced Code Blocks
Modify markdown-it’s fenced code renderer to enclose the rendered pre element in 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
The markdown-it plugin markdown-it-table-of-contents strips Markdown formatting from table of contents entries by default. Text formatting can be preserved by providing a custom implementation for getTokensText that renders the entry’s tokens:
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 can be used to generate custom containers, such as figures, listings, and blockquotes. The example below creates a source code listing with 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 with a figcaption element at the bottom of the figure. Unfortunately, the close renderer does not provide access to the caption available to the open renderer. Additionally, custom containers can be nested. Therefore we need to push the caption onto a stack in the open renderer and pop it in the close renderer:
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’s capabilities in regard to containers and nesting are rather limited. For example, some Markdown extensions for defintion lists do not support nesting paragraphs in the definitions, which can be done easily 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 enable syntax like this to define definition lists containing paragraphs in the definitions:
Paragraph before the definition list.
Term 1
: Definition of Term 1.
Term 2
: Definition of Term 2.
Paragraph after the definition list.Unfortunately, Markdown extensions for definition lists, such as @mdit/plugin-dl (this bug has been corrected) and markdown-it-deflist, do not support the generation of paragraphs inside the definitions. As a workaround, HTML can be used inside the Markdown document:
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 markup above to HTML, the paragraphs nested inside the definition elements are rendered correctly. However, Markdown markup contained inside the HTML p element is not rendered to HTML because of Markdown’s rule not to render inline Markdown markup contained in HTML block elements such as paragraphs. Eleventy’s Render plugin, which is included in Eleventy by default, can be utilized to pass the paragraph’s content to the Markdown renderer:
Adding the plugin in the Eleventy site’s configuration:
import { RenderPlugin } from '@11ty/eleventy';
export default function (eleventyConfig) {
eleventyConfig.addPlugin(RenderPlugin);
}Note that the HTML <p>…</p> markup needs to be omitted as the rendered Markdown will be enclosed by a p element automatically by the Markdown renderer:
Term 1
: {% renderTemplate "md" %}
The definition of Term 1 is _very_ short.
{% endrenderTemplate %}
Term 2
: <p>Definition of Term 2.</p>The Markdown markup above will be rendered to this HTML code:
<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>In order to render only inline markup to HTML without enclosing it in a block element, a paired shortcode calling an inline Markdown renderer could be utilized.