Tailwind CSS on 11ty
I don't think introductions are needed here, as, if you are on this page, it means you are looking for a proper solution to use Tailwind CSS on Eleventy static site generator.
Problem context
Tailwind can be installed via three main approaches: Vite, PostCSS or the CLI.
None of the three is well suited to a tool such as 11ty, where the "compilation" step doesn't call any well-known bundlers or frameworks.
Simple approach
The most straightforward approach would be to use the Tailwind CLI and some old-fashioned concurrently.
Just follow the Tailwind installation guide for the CLI and then add the build and start commands to your package.json, like the following:
{
//...
"scripts": {
"build": "npm run build:eleventy && npm run build:tailwind",
"build:eleventy": "eleventy",
"build:tailwind": "tailwindcss -i content/style.css -o _site/style.css --minify",
"start": "concurrently --kill-others -c='magenta,blue' --names='11ty,tailwind' 'npm run start:eleventy' 'npm run start:tailwind'",
"start:eleventy": "eleventy --serve",
"start:tailwind": "tailwindcss -i content/style.css -o _site/style.css --watch"
},
//...
}Finally, for a touch of developer experience, add a watcher on the Tailwind CSS stylesheet in your eleventy.config.mjs or similar:
const configureEleventy = (eleventyConfig) => {
// ...
eleventyConfig.addWatchTarget("./content/style.css");
// ...
}Although this approach kind of works, there are two major drawbacks:
if you use Nunjucks macros in your templates, those classes are not going to be picked up by Tailwind, unless you instruct it in the stylesheet to look for classes in the output folder, like
@import "tailwindcss" source("../_site");if you edit your stylesheet, you need to restart the 11ty dev server to observe the changes
Final solution
If you search online for people that have solved this problem, they are using PostCSS and hooking into the eleventy.before configuration event.
Having tried it, it works far better than the naive solution, but I still had problems with some classes in macros not being discovered. That's understandable: we are attaching to eleventy.before, which happens before the build stage. I switched to eleventy.after method, which fires at build complete.
See in the next section for the code.
TL;DR
Just write some supporting JavaScript method and place it in e.g. config/css.mjs:
import fsSync from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import tailwindcss from "@tailwindcss/postcss";
import cssnano from "cssnano";
import postcss from "postcss/lib/postcss";
const process = (inputPath, outputPath, logger, minify) => async () => {
const postcssProcessor = postcss(
minify ? [tailwindcss(), cssnano({ preset: "default" })] : [tailwindcss()],
);
const outputDir = path.dirname(outputPath);
if (!fsSync.existsSync(outputDir)) {
await fs.mkdir(outputDir, { recursive: true });
}
const cssContent = await fs.readFile(inputPath, "utf8");
const processed = await postcssProcessor.process(cssContent, {
from: inputPath,
to: outputPath,
});
logger.log(`Writing ${outputPath}`);
await fs.writeFile(outputPath, processed.css);
};
export const css = {
process,
};Then attach the css.process function to the event in your eleventy.config.mjs:
import { css } from "./config/css.mjs";
const configureEleventy = (eleventyConfig) => {
// ...
eleventyConfig.on(
"eleventy.after",
css.process(
"./content/style.css",
`./_site/style.css`,
eleventyConfig.logger,
isBuild,
),
);
eleventyConfig.addWatchTarget("./content/**/*.css");
// ...
}Note the watcher on the Tailwind stylesheet is still there.
Conclusion
I haven't noticed any compromise or annoying behavior with the final solution. If you happen to find one, let me know.