Three ways to add CSS to your Eleventy site
By default, Eleventy will generate HTML, but not CSS. You'll need to set this up yourself.
While there are many ways to add CSS, I've found three good options. They each have their advantages and tradeoffs, so which one is best will depend on your preferences and what you want your site to do.
The three options are:
- Have Eleveny copy your CSS files to the production build
- Processing CSS files with PostCSS
- Processing CSS files with Lightning CSS
The advantages (and tradeoffs) of using a CSS processor
While CSS is advancing rapidly, there are still a few advantages to using a CSS processor:
- They transform your CSS code to support older browsers.
- Because of point 1, you can use the latest CSS without worrying too much about browser support.
- They add vendor prefixes for any styles that are inconsistent between browsers.
- They minify your CSS so your users download a smaller file.
@import
rules are used to create a single CSS file.
The @import
rule is used to import style rules from other stylesheets, which allows you to organise your CSS in multiple files. But, used without a processor, it has a performance cost.
Most CSS files need to be downloaded by the browser before it can show the user any content. With HTTP/2, which is used by most static site providers, the browser can download multiple files at the same time. So if you have multiple small CSS files, they'll download almost as quick as a single file.
But if you use @import
, where one CSS file references another, they will be done one at a time. Each file will only be imported and downloaded after the previous one is completed.
Processors avoid this by importing code referenced by @import
into the base file, creating one CSS file with all the styles.
So, what are the tradeoffs of using a processor?
A processor adds dependencies and complexity to your site. You're relying on the person or team maintaining them to keep them working and secure. And they can be a bit of a black box, making some errors harder to find and fix.
Ok, now let's see how to set up the three different options.
Have Eleveny copy your CSS files to the production build
With a simple site you may find it easier to put your CSS in one or two (possibly more) CSS files and refer to them directly.
For our example we'll create a styles/styles.css
file in the source folder. During the Eleventy build this file will be copied to the build output used by the production site. We'll also add CSS files as a watch target, which will trigger an Eleventy build every time we save a CSS file, keeping our dev site in the browser up to date.
To set this up, add the following two lines to your Eleventy config file (you may have more in your file):
// eleventy.config.js
module.exports = function (eleventyConfig) {
+ eleventyConfig.addPassthroughCopy("./src/styles/");
+ eleventyConfig.addWatchTarget("./src/styles/");
return {
dir: {
input: "src",
output: "public",
},
};
};
Now you just need to add a link to the CSS file (or files) before the closing </head>
tag of each page. Normally you do this in your base template.
<link rel="stylesheet" href="/styles/styles.css">
And that's it. Simple!
Common CSS file set up when using a processor
Both the PostCSS and Lightning CSS examples are based on the same file structure: an index.css
file with all the other CSS files you're using imported using @import
rules.
Both processors will use these @import
rules to create a single index.css
file in your output folder, which contains all the styles from the imported files.
Processing CSS files with PostCSS
PostCSS has been around for years and is a popular choice for processing CSS files. Part of the reason for its appeal is that it's easy to use and has plugins for just about every type of processing you might want to do.
For this example we’ll use three plugins:
- autoprefixer to add vendor prefixes to CSS rules, so you don’t have to worry about differences between browsers when writing CSS.
- postcss-import which transforms
@import
rules to inline content in our output file. - cssnano to compress our CSS and make the final file smaller.
It’s really easy to add plugins, so add as many others you want.
To get started we need to install PostCSS and our plugins. You can do this from your terminal with the following command (make sure you’re in your Eleventy project directory):
$ npm install -D postcss autoprefixer cssnano postcss-import
Now you'll need to make two changes to your Eleventy config file (eleventy.config.js
):
- Add your imports to the top of the file
- Use PostCSS to process CSS within the
eleventy.before
function (this function runs every time Eleventy starts building)
At the top of the Eleventy config file, add the imports for PostCSS and its plugins, and also for the fs
library (we don’t need to install it):
// eleventy.config.js
+ const autoprefixer = require('autoprefixer');
+ const cssnano = require('cssnano');
+ const fs = require('fs');
+ const postcss = require('postcss');
+ const postcssImport = require('postcss-import');
// Rest of file...
To process your CSS, we'll use the JS API with a few simplifications, in the eleventy.before
function:
// eleventy.config.js
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const fs = require('fs');
const postcss = require('postcss');
const postcssImport = require('postcss-import');
module.exports = function (eleventyConfig) {
eleventyConfig.on('eleventy.before', async () => {
// PostCSS processing
+ const cssSourceFile = './src/styles/index.css';
+ const cssDestinationFile = './public/styles/index.css';
+ fs.readFile(cssSourceFile, (err, css) => {
+ postcss([
+ postcssImport,
+ autoprefixer,
+ cssnano,
+ ])
+ .process(css, { from: cssSourceFile, to: cssDestinationFile })
+ .then(result => {
+ fs.writeFile(cssDestinationFile, result.css, () => true)
+ });
+ });
+ eleventyConfig.addWatchTarget('./src/styles/');
});
// Rest of file...
};
What we're doing here is:
- Making it clear which file we want to use as our source file (
cssSourceFile
) - Naming our output file and where we want it (
cssDestinationFile
) - The plugins we're using are added in an Array to
postcss
. If you're using additional plugins, add them there. - When run, PostCSS will process our files and write them to our distination file.
- By adding the
src/styles
folder as a watch target, Eleventy will rebuild your site whenever you save a CSS file.
The last thing to remember is to reference the destination file in a link tag before your closing </head>
tag (normally in your base template):
<link rel="stylesheet" href="/styles/index.css">
Processing CSS files with Lightning CSS
Lightning CSS is "an extremely fast CSS parser, transformer, bundler, and minifier". It does the core things we want in a CSS processor, and does them fast.
Aside from being fast, one advantage of Lightning CSS over PostCSS is that it's a single dependency. So you can avoid issues with different options not working together. But you do give up the flexibility of PostCSS, and some of the features offered by the numerous plugins.
I chose Lightning CSS for this site, and followed the excellent guide by Stephanie Eckles to set it up. The guide gives options for adding Lightning CSS with or without SASS, and you can use a plugin Stephanie created or add the code yourself. I opted to add the code for Lightning CSS only.
Whether you add the code from Stephanie's article directly, or use her plugin, a couple of things to be aware of are:
- You need to prefix all your stylesheets, other than
index.css
, with_
(e.g._global.css
). This is because the_
at the beginning of the file names is being use to identify which files should be processed (parsed). browserlist
is added as a dependency, and is used to determine which browsers to target when transforming modern CSS features to work in older browsers.- You need to exclude the stylesheets from
collections.all
. In this example we're using thestyles
folder to save stylesheets, so in that folder we need to add:
// styles/styles.json
{
"eleventyExcludeFromCollections": true
}
In case it's helpful, the relevant parts of my Eleventy config file are shown below. Remember you also need to install both Lightning CSS and browserlist (npm install lightningcss browserlist
). For my browserlist I've used > 0.2% and not dead
(see this guide for more details).
// eleventy.config.js
const browserslist = require("browserslist");
const { bundle, browserslistToTargets } = require("lightningcss");
module.exports = function (eleventyConfig) {
// Recognise CSS as a template language
eleventyConfig.addTemplateFormats("css");
// Process CSS with LightningCSS
eleventyConfig.addExtension("css", {
outputFileExtension: "css",
compile: async function (_inputContent, inputPath) {
let parsed = path.parse(inputPath);
if (parsed.name.startsWith("_")) {
return;
}
let targets = browserslistToTargets(browserslist("> 0.2% and not dead"));
return async () => {
let { code, map } = await bundle({
filename: inputPath,
minify: true,
sourceMap: false,
targets,
drafts: {
nesting: true
},
});
return code;
};
},
});
return {
dir: {
input: "src",
output: "public",
},
};
};