11ty and Tailwind
11ty (Eleventy) is, as said on its website, a simpler static site generator, commonly used for blogs, documentation, personal and project websites. Paired with Tailwind CSS, an increasingly popular utility-first CSS framework, this combination of tools has truly proven in quick development and styling of content focused websites. In this article, we show you how to easily set up a project using these tools together.
Get Started
Since we’ll be using Node.js and Node Package Manager (npm), be sure you have downloaded and installed Node.js. You can check your installed version of Node by running node -v in the terminal of your choosing. 11ty requires Node.js version 14 or higher at the time of this writing.
The next step is to create a project directory and use it as a working directory. We’ll use the following terminal commands for this:
mkdir eleventy-tailwind-demo
cd eleventy-tailwind-demo
In our code examples, we’re using eleventy-tailwind-demo for the name of the project and its directory.
Finally, we need to add package.json to the directory. We’ll generate it by using the terminal command npm init -y. The -y flag skips the command-line prompts and uses the default options.
Install 11ty
Following the Getting Started section of 11ty’s documentation, we’ll install their package from npm registry, by running npm install -D@11ty/eleventy. The -D or –save-dev flag will install the package as a development dependency (in the devDependencies section of package.json). 11ty builds the static content files before you deploy them and is not required for hosting them in production.
To verify that Eleventy is properly installed, and everything is running fine run the next command npx@11ty/eleventy. The output will display the currently installed version of 11ty, which should be the latest one (at the time of writing, the version is v2.0.1). It also displays that 0 files were processed, since we haven’t created any, but that’s about to change.
Create Template
It’s time to create our first template, a content file written in one of 11ty’s supported formats, such as HTML. At this point it might be easier to switch to an editor for easier manipulation of files in the project directory. Our examples will use our editor of choice, which is VS Code.
In the root of the project directory create an index.html file with a default HTML template. You can use Emmet plugin to generate the code snippet by typing html:5. Afterwards, add Hello World! text to HTML body. Here’s the full code:
<DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
Hello World!
</body>
</html>
Running npx @11ty/eleventy now will compile this template and generate the static content in the _site folder. Since we will use that command often, we’ll create a package.json start script out of it and use npm run start instead.
// package.json
{
"scripts": {
"start": "eleventy --serve"
}
}
By adding –serve flag to our command, we’re able to view our webpage on http://localhost:8080. 11ty also watches for file changes, rebuilding them as soon as you save them.
Supported Template Languages
As previously said, 11ty templates support other formats and languages whose power you can utilize, such as Markdown (md), Liquid and Nunjucks (njk). In our examples, we’ll be using Nunjucks, its powerful ability to use data variables and filters.
First, we need to configure 11ty to use Nunjucks as a default template engine. We create, eleventy.js file in the root of the project which contains the following configuration:
module.exports = function (eleventyConfig) {
return {
templateFormats: ["md", "njk", "HTML"],
markdownTemplateEngine: "njk",
htmlTemplateEngine: "njk",
};
};
We’ll walk through the configuration, referencing the Configuration sections of 11ty’s documentation.
First, we define which format extensions we want to transform via templateFormats, setting its value to an array of strings holding md, njk, and html.
Next, we set the default template engine for Markdown (.md) files to Nunjucks via the markdownTemplateEngine property, making 11ty run the Markdown files through Nunjucks engine first, before compiling them to HTML with calculated values from Nunjucks’ data variables and such. The same thing is done for HTML via htmlTemplateEngine.
Source Folder
You might have noticed that both our index.html and generate output folder _site are on the same folder level. Since we’ll be creating more folders, it’s good practice to create a separate src folder and have all the source files in there.
After creating the src folder and moving index.html there, we need to update the 11ty configuration to use the src folder as input.
module.exports = function (eleventyConfig) {
return {
dir: {
input: "src",
output: "_site",
},
...
};
};
Layout
Now that we have our src folder, we can start creating a layout that should be reused and included in other pages. Thus, we first create the src/_includes/layouts subfolder and create a new base.html file by copying the index.html file there, which we’ll use as a starting point.
First, we’ll change the title tag. Since every page is going to include this layout, each page’s title should be overwritten with its own title, which is going to be read from the title data variable via Nunjucks syntax. We’ll do the same for the description meta tag.
...
<head>
...
<title>{{ title }}</title>
<meta name="description" content="{{ description }}" />
...
</head>
...
We need to instruct the base.html layout to render the wrapped page’s content, instead of always rendering the same "Hello World!" text. Thankfully, 11ty provides this content via content data variable, safely escaping characters via safe filter.
<body> {{content | safe}} </body>
Now, we can change index.html to use base.html layout.
layout: layouts/base.html
title: "11ty and Tailwind"
description: "This is an 11ty project styled by a utility framework called Tailwind."
---
<div>
<h1>{{ title }}</h1>
<p>{{ description }}</p>
</div>
Notice that index.html consists of two parts now: the HTML content at the bottom and Front Matter Data at the top. This data uses YAML syntax by default, but also supports JSON, JavaScript, and even custom formats. In this section, we provide the path to the layout file and values for the title and description to be used in that layout and the page itself.
As you might’ve noticed, the layout value doesn’t contain _includes, as we’re going to automatically prefix it via changing the 11ty configuration, like this:
module.exports = function (eleventyConfig) {
return {
dir: {
...
includes: "_includes",
},
...
};
};
When viewing your webpage in the browser, it should look like this:
Including Tailwind
To do styling with Tailwind, we need to install tailwindcss package via npm install -D tailwindcss.
Create a tailwind.config.js file in the root of the project with these params:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{html,js,md,njk}"],
theme: {
extend: {},
},
plugins: [],
};
You can also generate this config file with npx tailwindcss init. Just be sure to include all of the above extensions (html, js, md, njk).
Create src/tailwind/tailwind.css with following content:
@tailwind base;
@tailwind components;
@tailwind utilities;
Compile this css file via tailwindcss command, with src/assets/css as target folder:
npx tailwindcss
-i ./src/tailwind/tailwind.css
-o ./src/assets/css/tailwind.css
Add link to these generated css file in base.html
...
<head>
...
<link rel="stylesheet" href="/assets/css/tailwind.css" />
...
</head>
...
The assets folder is where we store all static assets, such as public images, html files, and stylesheets. In our cases, we’ll only store stylesheets as regular CSS files generated via Tailwind. We need to change the configuration again to copy our assets folder to the output folder via a Passthough File Copy method:
module.exports = function (eleventyConfig) {
eleventyConfig.addPassthroughCopy("src/assets/");
...
};
Build Scripts
We can make our life easier by creating build and watch scripts in package.json, so we don’t have to write them again and again. It saves us from searching for typos and makes creating CI/CD pipelines easier.
{
...
"scripts": {
"build": "npm run tailwind:build && npm run 11ty:build",
"11ty:build": "eleventy",
"11ty:watch": "npm run 11ty:build -- --serve",
"tailwind:build": "tailwindcss -i ./src/tailwind/tailwind.css -o ./src/assets/css/tailwind.css",
"tailwind:watch": "npm run tailwind:build -- --watch",
},
...
}
To run multiple processes concurrently we can use another npm package – concurrently. Install it first with npm install –D concurrently.
{
...
"scripts": {
...
"start": "concurrently \"npm run tailwind:watch\" \"npm run 11ty:watch\"",
...
}
}