Using Tailwind CSS
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.