About 4 min reading time 02/10/2022

Using PostCss and Tailwindcss in 11ty

One of the first things I check on every new technology, that I checked out recently is the support for PostCSS integration. TBH I'm not a big fan of PostCSS at all but TailwindCss works best with a PostCSS setup. So I investigated some minutes before I started to setup my blog with 11ty to find out, how PostCSS integration is possible and the result was surprisingly simple and it took me about ~30 min of research and implementation.

TL;DR: Checkout .eleventy.js config file of this blog (It is really simple).

Setup

Lets begin with the obvious parts: Installation of the dependencies ;)

yarn add -D tailwindcss postcss

The key of my setup is the usage of a "async nunjuck template filter" that processes a css string by applying postcss to it.

.eleventy.js

module.exports = eleventyConfig => {
// Registering a async filter to all nunjuck templates
// unfortunally it didn't saw the possiblity to register
// 'global' async filters (say for all other templating engines
// but I'm fine with this solution
eleventyConfig.addNunjucksAsyncFilter('postcss', (cssCode, done) => {
// Here is where the magic will happen
})
}

The snippet above shows how to register the filter with the name 'postcss' - configured with the first parameter (if you read the 11ty docs might guess the source for my inspiration). The second parameter is the callback that will actually run the filter, it receives the cssCodeto process and a done callback which must be called in order to tell the template engine that the filter finished.

Before you are going to implement the actual body of the filter you should integrate the css to your template in order get better understanding what happens when the filter is invoked. I added the following snippet into the default layout content/_includes/layouts/index.njk:

  <head>
<!-- capture the CSS content as a Nunjucks variable -->
{% set css %}
{% include "styles/index.css" %}
{% endset %}
<!-- feed it through our postcss filter to minify -->
<style>
{{css | postcss | safe}}
</style>
</head>

This technique includes two steps in the 11ty build pipeline:

The approach implies that your CSS is directly included into your document and this comes (like the most things in computer engineering) with up- and down sides which we'll discuss later.

Integrate PostCSS in the Filter

No it‘s time to add the actual code for the postcss filter. Fortunately PostCSS provides a relatively simple programmatic api which we use to postprocess the CSS contents.

.eleventy.js

module.exports = eleventyConfig => {
eleventyConfig.addNunjucksAsyncFilter('postcss', (cssCode, done) => {
postCss([
// plugins can be added heres
])
.process(cssCode)
.then(
r => done(null, r.css),
e => done(e, null)
)
})
}

Since the filter callback provides it‘s own done callback rather than allowing to return a promise, it‘s necessary to invoke it within the then function of the promise returned from process

Add Tailwindcss

One of the coolest „plugins“ in PostCSS is [Tailwindcss], a utility first css framework. And with the current setup it is pretty easy to add this feature to the pipeline:

.eleventy.js

const tailwind = require(„tailwindcss“)

module.exports = eleventyConfig => {
eleventyConfig.addNunjucksAsyncFilter('postcss', (cssCode, done) => {
postCss([
tailwind({
// configuration for tailwind can be added here
})
])
.process(cssCode)
.then(
r => done(null, r.css),
e => done(e, null)
)
})
}

It‘s possible to add additional configuration for tailwind. For a (mostly) markdown based blogging environment like 11ty I'd add the @tailwindcss/typography plugin:

  tailwind({
plugins: [
require('@tailwindcss/typography')
]
})

And that's it! You can now add the Tailwind directives to styles/index.css and start to build your design.

One more thing

You might encounter that the changes in css doens't trigger a rebuild of 11ty. This is because the file is (probably) neither in the input directory nor having a known fileextension. Fortunately 11ty provides a simple configuration method to close this gap:

  eleventyConfig.addWatchTarget("styles/**/*.css");

The method registers a new glob which tells 11ty to watch for changes and rebuild when ever they happen.

Alternative / Extension

The result of the presented approach is that all CSS is inlined within every document rather than loaded as external resource. TBH, it might not have a too big impact in a small blogging setup with a small amount of CSS like this blog in the moment (to have really small css, some more postcss plugins are required like purgecss and minifycss). But maybe your use-case is different or you're just curios.

The 11ty quick tip about CSS concatenation shows an approach to bundle CSS data into a separate CSS File which can be loaded traditionally in <link /> tag.

Here is an incomplete +/- list for for inlined CSS:

Advantages:

Disadvantages:

The approach to create the inline CSS combined with the permalink configuration of 11ty makes it handy to implement a loadable css file. Just create a template file and roughly add the following contents:

---
permalink: theme.css
---


{% set css %}
{% include "styles/index.css" %}
{% include "styles/prism-theme.css" %}
{% endset %}
{{css | postcss | safe}}

Now the css can be loaded as a file with: <link rel="stylesheet" href="/theme.css" />