Using Tailwind CSS

geekery

A while ago I came across a link to a new CSS framework called Tailwind CSS. What I read intrigued me. I’ve used a number of different frameworks to style this site, including (most recently) Skeleton and also the Bourbon Sass toolset. The tools certainly made styling the site how I wanted and also making it behave nicely on different screen sizes easier than plain old CSS, but I still came up against frustrating problems that I found hard to fix because I don’t understand CSS in enough depth. Tailwind looked interesting, so I decided to give it a whirl. When I redesigned my photography site, I used another framework (Tachyons) with a similar rationale to Tailwind, but I like Tailwind much better.

Tailwind is unusual in that it provides low-level utility classes which you insert directly into your HTML files. So, for example, if you wanted to add 2rem of top margin to a div element, you would use the following:

<div class="mt-8">
  Hello
</div>

Tailwind has a number of well-thought out levels for each of its utility classes, so margin and padding (and other spacing utilities) run from m-0 which is 0 to m-64 which is 16rem. Similarly, there are pre-defined colours, each with the same 9 shades, running from say text-gray-100 (very light grey) to text-gray-900 (almost black). This could feel restrictive, but actually I found it meant that I wasn’t dithering over small differences but picking a small number of values that worked well and could be used consistently. You can also add your own values (if you wanted shades of a particular colour for example).

Tailwind has responsiveness built in, with a base size which applies to all screen sizes, then 4 additional breakpoints which apply to screens of that size or larger. To use this, you just prepend the breakpoint size to the utility class. So building on the example above, if we wanted 2rem of top margin on our div at all screen sizes, but then 2.5rem on a large screen and 3rem on an extra large screen, we would use the following:

<div class="mt-8 lg:mt-10 xl:mt-12">
  Hello
</div>

This was a real revelation to me. It makes it really easy to focus first on getting the design to work well on the smallest screens, then incrementally change only what is needed to take advantage of progressively larger screens. The whole design process is really smooth because you don’t have to worry about the CSS cascade process. You can just focus on adding the utility classes to the elements you want to style. Using Hugo for this blog and running the development server means that as soon as you save the file, the preview of the page in the browser updates. This makes it easy to tweak: do I want mt-8 or mt-10? Easy — just try one, see what it looks like, edit the 8 to a 10 and look at it again. You can take a kind of ‘Goldilocks’ design strategy of ‘too much, too little, just right’, and quickly flip between values to see what works best.

I have to say that I didn’t start from scratch. Dirk Olbrich has produced a very nice Hugo Starter Theme skeleton incorporating Tailwind, which includes the Hugo Pipes plumbing to configure Tailwind as part of the build process. It also provides a nice example of a ‘hamburger menu’ (the icon with three horizontal lines) on small screens which enables you to dynamically hide and show the menu of links without using Javascript. I definitely wouldn’t have been able to figure that out on my own, and it works very nicely on a phone.

I came across only two difficulties when getting everything set up. The first issue was how to style HTML elements within each generated post. The templates for each page can be predefined, but each post is generated from the Markdown files I write, and so I can’t easily add classes to those. The solution is to use @apply to apply Tailwind’s utility classes to your own elements and/or classes in a CSS file. The starter theme sets this up by importing a site.css file in the main Tailwind style.css file. This enabled me to style things like blockquotes, code blocks and italic/bold text in the posts themselves, without having to add classes to the HTML. So, for example, here’s how I style my blockquotes:

blockquote {
@apply my-6 pl-4 italic border-l-4 border-gray-300;
}

The second issue is that Tailwind — by design — includes all of the utility classes in the generated CSS file by default. This means that you can potentially use all of them while you are designing the site, but the intention is that you use something like purgecss in ‘production’ mode to remove all the unused classes and slim down the CSS file. This is definitely needed, as the full CSS file is several MB in size, and you don’t want visitors having to load an enormous file on each visit.

The Hugo starter theme included some useful code in the postcss.config.js file, which sets up the tool chain for building the CSS file and purging unused styles. However, I couldn’t initially figure out why my blockquote styles (for example) looked fine on my local machine but didn’t work when I published the site to Netlify. Eventually I realised that purgecss wasn’t saving the selectors in my site.css file, and so that styling wasn’t being included in the generated CSS in production mode. This is because the configuration searched the template HTML files for classes to keep, but didn’t look in the site.css file. Adding a whitelist array with the classes and selectors you want to keep worked, but I found a better way documented in the Hugo documents. If you add the following to your config.yaml file, it will generate a file called hugo_stats.json, which lists all the classes and element selectors used.

build:
  writeStats: true

You can then tell purgecss to use this file to tell it what Tailwind classes it should keep. My final postcss.config.js file looks like this:

const themeDir = __dirname + '/../../';

const purgecss = require('@fullhuman/postcss-purgecss')({

    // Specify the paths to all of the template files in your project
    content: [ './hugo_stats.json' ],
    defaultExtractor: (content) => {
        let els = JSON.parse(content).htmlElements;
        return els.tags.concat(els.classes, els.ids);
    }
})

module.exports = {
    plugins: [
        require('postcss-import')({
            path: [themeDir]
            }),
        require('tailwindcss')(themeDir + 'assets/css/tailwind.config.js'),
        require('autoprefixer')({
            path: [themeDir]
        }),
        ...(process.env.HUGO_ENVIRONMENT === 'production' ? [purgecss] : [])
    ]
}

The Hugo docs also provide some information about how to set up the build process on Netlify so that it uses the production environment (and therefore uses the purged CSS file). I still haven’t quite got to grips with this. If I build in production mode on my own machine so that it generates and caches the CSS file in the resources directory, it works fine. If not, I get errors and the build fails. As I understood it, Netlify should build and generate the CSS itself in production mode, so I am obviously missing something. Anyway, it is easy to work around and the final result works how I expect.

If you need to style a site, I would definitely give Tailwind a go. There’s a great series of screencasts which do a good job of demonstrating how to use it for a realistic task, and I learned a lot of tips and tricks from it.