Skip to content

Use Eleventy Templating To Include Static Code Demos

Posted on: Apr 2, 2021 by Stephanie Eckles
🍿🍿🍿 15 min. read

Create a single-page site that composites working code examples by leveraging Nunjucks templating abilities and custom collections and shortcodes.

This post assumes a foundational familiarity with Eleventy. If you're new to 11ty - welcome! You may want to start here.

This tutorial will showcase many features of Nunjucks templating. So, even if the tutorial's goal doesn't match your use case, you'll get more familair with using it as a templating language!

Project Brief

The goal of this Eleventy project is to create a single-page site that displays live code demos, like the setup on SmolCSS.dev. And we'll see how to extend it to a scoped context like is used for this ModernCSS.dev tutorial.

Here are the base requirements:

Base Project Configuration

We're starting from an empty project with Eleventy installed and ready to run.

This tutorial will assume a customized input directory of src, and that you've created an index file where we'll eventually add the demo content.

If you're unsure how to do those things, follow my tutorial on creating your first 11ty site up until the Create the Blog step, and then come back to continue! 😊

You may also want to do the "Create a CSS File" and "Link to Stylesheet" sections in that tutorial if you are unfamiliar with how to do that with Eleventy.

Setup your index as index.njk as we'll primary be using Nunjucks for this tutorial.

Base Eleventy Setup for Single-Page Content

My favorite thing about Eleventy is the flexible content modeling. And most projects I do definitely benefit from the filesystem page creation, but for this project we'll need to disable that functionality.

So, our first step is to create a location for our code demos to live. Add src/demos, and then create the file demos.json with the following contents:

{
"tags": "demos",
"permalink": false
}

This is a directory data file and we define tags to create the demos collection. Then we set permalink to false to prevent page generation. These values will apply to all content within the directory, although you could still set front matter to override them for specific content.

Create a Demo File

Let's create our first demo. Add demo/centering.njk with the following content:

---
title: "CSS Centering"
order: 1
date: 2021-03-31
templateEngineOverride: njk, md

---

We include an order property that we'll use to order our demos within our single page rather than relying on Eleventy's default of date-based ordering. This is because we may create multiples on the same day, and on publish they may change order from what we see locally.

There is also a date included for purposes of correctly updating the RSS feed we'll be adding.

Finally, we add templateEngineOverride: njk, md because we will be mixing Markdown with Nunjucks content in this file and we want both to be processed correctly.

Demo Partial and Code Content

You may be wondering why we didn't add any content in the previous step. Instead of directly outputting the content within the demo Nunjucks file, we're going to populate Nunjucks variables to pass into a partial. This will enable consistency among our code demos and allow enhanced functionality.

Create src/_includes/demo.njk which will be our partial.

Let's determine the parts we need to output for each demo:

Now, since this is a case study of SmolCSS.dev, we're going to focus on showing the CSS as highlighted code. You can extend the following ideas to also show the HTML, or perhaps only the HTML, or even add JavaScript into the mix.

Within demo.njk let's populate our demo template:

{{ description | safe }}

<style>
{{ css | safe }}
</style>

<details>
<summary>CSS for "{{ title }}"</summary>

{% highlight "css" %}
{{- css | safe }}
{% endhighlight %}
</details>

<div class="demo">
{{ html | safe }}
</div>

Now this partial accepts the following variables:

Let's go back to our first demo file and add the content within these variables:

{% set description %}

**Put down the CSS centering jokes**! This modern update is often the solution you're looking for to solve your centering woes.

{% endset %}

{% set css %}
.centering {
display: grid;
place-content: center;
}
{% endset %}

{% set html %}
<div class="centering">
<span>Feeling Centered</span>
</div>
{% endset %}

{% include "demo.njk" %}

This sets the variables, and also includes the demo partial. For each demo file you create, continue this pattern of setting variables and including the partial.

Before we see some example output, this also assumes you are using eleventy-plugin-syntaxhighlight and have pre-included a prism theme.

Here's some starting CSS for the details, summary, and .demo:

details {
margin: 2rem 0 0;
}

details pre[class*="language-"] {
margin: 0;
}

summary {
padding: 0.15em 0.5em;
background-color: #0d3233;
color: #fff;
cursor: pointer;
}

summary:focus {
outline: 2px solid #0d3233;
outline-offset: 2px;
}

.demo {
min-height: 30vh;
padding: 1rem;
border: 2px dashed currentColor;
}

Example Code Demo Output

Put down the CSS centering jokes! This modern update is often the solution you're looking for to solve your centering woes.

CSS for "Demo Output"
.centering {
display: grid;
place-content: center;
min-height: 30vh;
}

.centering span {
padding: .5em;
outline: 2px solid;
}
Feeling Centered

Extend the Code Partial

The great thing about handling this via a partial that is passed variables to modify behavior is that you can build up more variables to alter the output when needed.

For example, maybe you don't always have HTML and description output and just want to show the CSS. So, you could create and handle for a hideDemo variable:

{% if not hideDescription %}
{{ description | safe }}
{% endif %}

{% if not hideDemo %}
<div class="demo">
{{ html | safe }}
</div>
{% endif %}

Which would be set as follows within your code file:

{% set hideDescription = true %}
{% set hideDemo = true %}

Resulting in only the details/summary appearing:

CSS for "Demo Output"
.centering {
display: grid;
place-content: center;
min-height: 30vh;
}

.centering span {
padding: .5em;
outline: 2px solid;
}

Challenge: Extend this to make the open property for details a variable so that you have the option to set the details to visible instead of collapsed

Ordering and Output of the demos Collection

Now that we have our demo code files and partial figured out, it's time to figure out how to display this content. Remember - we turned off the default file system individual page creation.

We also had created a collection called demos when we defined the directory data file by assigning all of the files the tag of demos.

However, that collection will currently be date based as is the 11ty default. Since we want it based off the order front matter, we need to create a custom collection.

Add the following within .eleventy.js above the previous customization that updated the input/output directories:

eleventyConfig.addCollection("orderedDemos", function (collection) {
return collection.getFilteredByTag("demos").sort((a, b) => {
return a.data.order - b.data.order;
});
});

This takes the demos collection and reorders it by our custom order key and returns a new collection called orderedDemos.

Now we can loop through the new collection to output it within our main index:

{% for demo in collections.orderedDemos %}
<article>
<h2 id="{{ demo.fileSlug }}">{{ demo.data.title }}</h2>
{{ demo.templateContent | safe }}
</article>
{% endfor %}

This is a minimal example where we are adding fully compiled demo templateContent within an article. We also pass in the main demo front matter title within an h2 that includes an id set to the demo's file slug in case you'd like to enable anchor links to each demo.

RSS Feed

To setup an RSS feed, install the eleventy-plugin-rss plugin and follow the instructions and use the example code from the RSS plugin docs.

The only alteration is to change from the posts collection to use our orderedDemos collection to correctly order the feed. In addition, you'll need to pass the reverse filter, resulting in the following start of the feed loop:

{%- for item in collections.orderedDemos | reverse %}

"Open in CodePen"

A bit of functionality that provides a valuable extra resource for our readers is the ability to export the demo code to CodePen.

I've abstracted my solution as used on SmolCSS.dev and ModernCSS.dev into the plugin @11tyrocks/eleventy-plugin-open-in-codepen.

Here's an example of configuring the plugin:

const openInCodepen = require("@11tyrocks/eleventy-plugin-open-in-codepen");

module.exports = (eleventyConfig) => {
eleventyConfig.addPlugin(openInCodepen, {
siteUrl: "YourSite.dev",
siteTitle: "Your Site",
siteTag: "yoursite",
buttonClass: "your-button-class",
buttonIconClass: "your-button-icon-class",
});
};

Which would then make the shortcode postToCodepen available within your partial. You can include the button with the following updates:

// Set in your code demo file
{% set slug = page.url %}

// Add where you'd like it to appear in the partial
{% postToCodepen title, slug, css, html %}

Here is a working example of the button created by the shortcode (and including customizations specific to 11ty Rocks!) that will export the demo that we set up in this tutorial:

Prevent Global Affects on Demo CSS

If your use case is to populate demos within a context where you don't want your site's global CSS to cascade and affect your demo CSS, we can add a shortcode to help scope the styles.

But first, here's a starting set of styles for our .demo contents to reset most cascaded properties. You may find you need to add more properties here depending on what elements you are using in the demo. The goal is to prevent most global styles leaking through (and demo styles leaking out!) so that the demo styles are closer to being fresh from a native browser style base. If you are using a style reset, those rules may still come through for certain elements which is likely desireable.

.demo * {
margin: 0;
padding: unset;
border: unset;
border-radius: unset;
color: unset;
font: unset;
font-family: system-ui, sans-serif;
}

Next, we'll create the shortcode. A shortcode can output content, and we'll actually use it to output both the <style> and .demo blocks, replacing what we previously had for the HTML in the partial.

Here is the content of the codeDemo shortcode, which intakes the css and html (if available). Then it finds the CSS class names, and replaces them with a version that has a "hash" (just a simple random number string for this context). Following that, it traverses the HTML and updates the matched CSS class names so that the hashed CSS selector matches the demo HTML.

eleventyConfig.addShortcode("codeDemo", function (css, html) {
if (!html.length) return "";

if (!css) {
return `
<div class="demo">
${html}
</div>
`
;
}

const hash = Math.floor(Math.random(100) * Math.floor(999));

const cssRE = new RegExp(/(?<=\.)([\w|-]+)(?=\s|,)/, "gm");
const cssCode = css.replace(cssRE, `$1-${hash}`);

let htmlCode = html;
css.match(cssRE).forEach((match) => {
// prettier-ignore
const htmlPattern = match.replace("-", "\\-");
const htmlRE = new RegExp(`(${htmlPattern})(?=\\s|")`, "gm");
htmlCode = htmlCode.replace(htmlRE, `${match}-${hash}`);
});

return `
<style>
${cssCode}</style>
<div class="demo">
${htmlCode}
</div>
`
;
});

A couple notes:

Then in the partial, remove our previous block that output the HTML .demo and replace it with the following:

{% set htmlCode %}{{ '' if hideDemo else html | safe }}{% endset %}

{% codeDemo css, htmlCode %}

This still takes into account the option to hide the demo.

Important note about Nunjucks variables: They will pass through multiple instances of a partial if you include the partial multiple times within one file. If you are writing a tutorial and set the html variable at the top of the file, you can repeatedly update the css variable only prior to including the partial again and it will use the original HTML up until you update the html variable. This is super useful for tutorials showing how to progressively build something. In fact, this technique was created for exactly that purpose over on this tutorial for ModernCSS.dev.

Phew! That was a lot to learn about Nunjucks templating 😊 I'd love to know if this was helpful to you and see what you make with it! Reach out to @5t3ph on Twitter