Extending 11ty’s page variable

perishable: best read by Nov 20, 2023

I’ve had my eye on Astro, so I thought I’d take it for a spin by re-building this here website. I stuck with 11ty in the end for a variety of reasons, but the experience inspired me to see if I could replicate a few Astro niceties in 11ty. Turns out yes, thanks to 11ty’s page variable.

single file templates, kind of

11ty’s new WebC format uses single file templates out of the box, but Liquid and Nunjucks require a little more creativity. My use case for single file templates is more about having an easy place to drop little bits of page-specific CSS vs. a full-blown asset pipeline. Totally doable by extending 11ty’s page variable.

Step one is creating a paired shortcode to the 11ty config. I like to keep my filters and shortcodes in separate files to keep things tidy:

const postcss = require('postcss');
const nesting = require('postcss-nesting');
const csso = require('postcss-csso');

module.exports = function style(content) {
postcss([
nesting,
csso,
]).process(content, { from: 'undefined' }).then((result) => {
this.page.style = result;
});
return '';
};

I’m using PostCSS for nesting syntax and minification, but you can skip that and return plain CSS if you want. Now we need to tell 11ty about our new shortcode:

module.exports = function(eleventyConfig) {
const style = require("./_source/_utilities/style.js");
eleventyConfig.addPairedShortcode('style', style);
};

Finally, add little conditional code in your layout file’s <head> element:

<head>
{% if page.style %}
<style>
{{ page.style }}
</style>
{% endif %}
</head>

That’s it for plumbing. Here’s how you use it in a Liquid template:

<!-- front matter and template content -->
{% style %}
.foo {
position: absolute;
top: 0;
}
{% endstyle %}

Any CSS you write in the shortcode is added to a style tag in the head of the page. It’s a nice and simple way to add page-specific styles without bloating you main CSS bundle.

multiple slots

Another nice thing about Astro is the concept of multiple slots. Out of the box, 11ty only supports one content slot, which can made more complex layout needs hard to pull off without making markup concessions.

For instance, on this site I needed a way to include some markup outside of the main element where I’m rendering content. What to do? Once again, extending the 11ty supplied page variable with a paired shortcode is the answer:

module.exports = function setVar(content, name) {
this.page[name] = content;
return '';
};

Unlike our style shortcode, this variation allows for setting a variable name in order to support additional slots. Like with the style shortcode, we need to hook up our slot by adding a conditional where it’s going to be used:

{% if page.pattern %}
{{ page.pattern }}
{% endif %}

I’m using pattern as the name since that’s what I’m using it for, but you can use whatever names make sense to you. Here’s how you use it in a Liquid template:

{% setVar 'pattern' %}
<random-pattern quantity="10" jitter="150"></random-pattern>
{% endsetVar %}

Worth noting here that Liquid.js does have a concept of multiple blocks but I couldn’t get it to work properly in my testing. Plus, this technique has the benefit of not wholesale changing how you do layout in your project.

Read more notes