Why and how you should use Tailwind to build your next Statamic theme

12th Sep '1816 of your Earth minutes

Why and how you should use Tailwind to build your next Statamic theme

I have been building out a custom eCommerce solution in a Statamic (“stat-a-mic” or “sta-tamic”? Dunno) website for the past few months and recently started on the build of the front-end part of the site.

It’s been a tough project because of time constraints with a hard deadline to tie in with some exciting company news. So a rapid build was crucial.

I’d also dipped my toes into the Tailwind CSS framework and was so excited by it that, even though it’s still in a pre-release stage, I wanted to use it for the theme of the site.

The good news is that with all of these tools, we managed to build something I’m truly proud of and can’t wait to show everyone.

However, in my hunt to make a quick start, I couldn’t find much help out here for integrating Statamic and Tailwind, so now that I have some time I wanted to write up some of my notes in the hopes that it helps others.

I also can’t stress enough that you should try Statamic ❤️. It’s a brilliant CMS, it has a very supportive community and v3, which is just around the corner, looks set to be one of the most flexible and powerful tools I will have ever had the pleasure of using.

Ok, gushing over.

Now, Tailwind is no stranger to Statamic; it’s already heavily used in the Control Panel and it’s been ‘bundled’ in some of the themes.

I say ‘bundled’ because if truth be told, it’s not really put to any great effect in those themes. I mean, it’s there sure, but a lot of the really cool stuff you can do with Tailwind has been left to the developer.

And that’s absolutely fine. These are free themes and they’re all pretty well advertised as “starter” themes, so this is understandable.

Pick one of these or start from scratch. But however you get started with your next theme, I’d like to offer you some extra steps that may help save you some fuss.

That’s why in this post, I want to cover how to set up your Statamic theme to use Tailwind and start writing the best darn CSS you’ve ever written. Spoiler alert: that might be less than you think!

One of the first things I thought when I saw Tailwind utility classes in HTML was…

“Yuck! Look at all those class names.”

This was almost the same problem I’d had with BEM and why I never went beyond only reading about it. (That and all of the dashes and underscores)

With Tailwind, I was really keen to get past that initial disgust and use it in earnest for a few reasons.

Sure, it was being built and used by some developers I follow who are pragmatic, make smart choices and whose opinions I respect (so was BEM).

But primarily it was for the end result: building web designs (HTML & CSS) that offered developer flexibility, simplicity of expression and ease of maintenance, modification, and reuse.

Priority number 1 though has to be providing a better experience for users. This translates to:

Of course, I’d come from the “best practice” world of “separating my concerns”, modified slightly by the period of CSS pre-processor enlightenment, much like Adam Wathan. Terse, reusable class names balanced against ‘clean-looking’ HTML was a worthy goal to strive for, and I would agonise over making the HTML as minimal as possible.

Then I moved onto Bootstrap in a big way. This was great for a while, but I became very conscious that there was a lot of stuff I wasn’t using and what I was using I was heavily overriding. Plus my markup was bloating to fit the rules of the framework rather than my markup taking the lead and styles adding the visual icing. This doesn’t feel like the right way to frontend anymore.

Tailwind appeared to offer something I’d not seen anywhere else. With some extra tricks, I believed that I could get to my near-ideal front-end development environment.

Let’s go.

Fair warning: I’ve only been able to achieve all of this by using some other tools which you may or may not be familiar with: Sass, PostCSS, Webpack and Laravel Mix. You won’t need to be overly familiar all of these tools (I’m certainly not!), but you will need a working setup of Node and NPM to get building.

I feel I should apologise at this point, because NPM is a beast and suddenly introducing thousands of packages into your project is not ideal. But trust me when I say that the power that comes with all of those hundreds of MBs is absolutely worth it — despite the headaches.

I’m a Laravel developer and I wanted to keep a similar workflow to my Laravel projects with this, so thats a big driver behind my implementation and tech choices.

If that makes sense to you, then hopefully this article will be really easy to follow. Even so, none of this should be too strenuous if you’ve done any front-end development in 2018.

Finally, if you’ve got time, I’ve got a little bonus at the very end to help you bring your final CSS file size down massively, taking into account all of your Statamic content.

Open your sails

This isn’t going to cover how to get started with Tailwind. The Tailwind crew have already done a much better job of that than I could.

So ideally you already know a bit about Tailwind. If you do, I’ve got a lot less convincing to do.

If you don’t, stay here for now and I’ll get you started, then when you’re ready… go have a play. Once you get used to Tailwind, you’ll wish you had this to build every website you’ve ever built. So long to the ‘old’ way.

Blow me to Bermuda!

The Right Way™ to use Tailwind within Statamic is as part of your theme.

We’re going to start with the Storyteller Statamic theme, which is a free starter theme from the Statamic Marketplace. I’m starting here because it gives you something nice to look at, but you’ll probably want to start from scratch if your site is a completely custom design. Up to you.

Typically, once you’ve downloaded the theme and installed it in your Statamic site, the next step is to initialise NPM. But let’s hold off from downloading the theme for a few minutes and do some exploration…

What’s going to be installed? Let’s have a look at the theme’s package.json:

2 "private": true,
3 "scripts": { ... },
4 "devDependencies": {
5 "cross-env": "^5.1",
6 "laravel-mix": "^1.0",
7 "less": "^2.7.3",
8 "less-loader": "^4.0.5"
9 },
10 "dependencies": {
11 "tailwindcss": "^0.4.0"
12 }

Right, so (as of September 2018) this theme is fetching a few outdated resources. So let’s update some bits.

Perhaps we should get the latest versions of some of those. Also, it’s using Less for CSS pre-processing, but I prefer Sass — so I’d like to swap that out (of course, you can keep Less if you prefer or drop preprocessors altogether). So here’s an updated package.json we could use.

I’ve added a couple of tools we’ll be using later and updated the scripts array to those recommended by Laravel Mix.

2 "private": true,
3 "scripts": {
4 "dev": "npm run development",
5 "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
6 "watch": "npm run development -- --watch",
7 "watch-poll": "npm run watch -- --watch-poll",
8 "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
9 "prod": "npm run production",
10 "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
11 },
12 "devDependencies": {
13 "cross-env": "^5.2.0",
14 "glob-all": "^3.1.0",
15 "purgecss-webpack-plugin": "^1.2.1",
16 "laravel-mix": "^2.1.14",
17 "node-sass": "^4.9.3",
18 "tailwindcss": "^0.6.5"
19 }

Some other things you might then want to update include:

The simplest way to do all of this is to download this pack that I made earlier: Storyteller 2.0, if you will.

Once you’ve copied the theme files into place, you can run the build:

1cd /path/to/site/themes/storyteller/
3npm install && npm run dev

When NPM finally finishes downloading everything, the build should run and the CSS will be placed in /public/themes/storyteller/css/storyteller.css

At this point, you should be able to load the site and everything should look the same as the original Storyteller theme.


That’s actually all there is to it! You can now go and build a beautiful theme using Tailwind.

What I have for you at this point are some tips for how to structure your theme and minimise your CSS as much as possible; lessons learned from weeks of building a Statamic theme from scratch.

Keep all of your theme assets together

This is probably super obvious, but it’s really useful to keep all of your theme-specific assets together in the theme folder. You don’t want half of your images in /assets and half in /site/themes/storyteller/images — anything that needs to be variable and based on CMS content should be a field in a fieldset somewhere, pulled in by your theme.

One of the conveniences that Statamic has is to route requests to yourdomain.com/site/themes through to the site folder, even though it’s above the public root. I’m not a big fan of this for a couple of reasons:

  1. It creates a dependency on a specific web server configuration — I know that’s not that bad, it’s just another thing to remember in case you can’t use .htaccess or some other configuration convenience.
  2. We might inadvertently make some source files available publicly that we hadn’t intended for public consumption.

For these reasons, I prefer the compiled CSS (and JS) to be placed in /public. So in Storyteller 2.0, I’ve bundled a webpack.mix.jsthat does this in a way that will work with multiple themes.

Thanks to the magic of Statamic filesystems, all you then need to do is make sure your Themes Filesystem URL points to /themes instead of /site/themes and then you can continue to use the {{ theme:[css|js|asset|output] }} tags in your templates.

Use Tailwind’s utilities properly

My initial approach with Tailwind was to extract as many components out from its base styles as possible. I’d kind of viewed Tailwind as a tool to create my own completely custom version of Bootstrap.

This helps when your goal is to go from class-stuffing:

1<button class="bg-blue hover:bg-blue-dark text-white font-bold py-2 px-4 rounded">Click me!</button>

to component classes:

1<button class="btn">Click me!</button>

But it doesn’t really help if your goal is to reduce your CSS. Why? Because browser CSS interpreters can’t reference class rules (yet): btn can’t simply point to all of those other class names and inherit their styles. So Tailwind has to actually copy all of the styles from those other classes into a new class rule.

That means that the more component classes you create, the more actual code is generated.

So my recommendation here is to prefer using the utility classes in your HTML where possible over creating component classes. Your final combined page size will be much smaller if you do.

Of course, that makes the most sense for one-off elements that aren’t yet being re-used. As soon as you have complex, multi-layered components that have the same styles being used in multiple places, not only does it become brittle to apply the same styles in multiple places, it shifts the weight problem to the other side of the see-saw: now your class names in HTML attributes might be adding more bytes to your page size than the CSS rules they would otherwise rely on.

When that happens, extract the relevant styles to a component. Unfortunately there’s no silver bullet here and this is down to you knowing your design and your project’s goals.

Don’t forget your pre-processor!

I’m so used to Sass that moving wholly over to another tool was going to be too many changes in one go for me. The good news is that Tailwind uses PostCSS in Laravel Mix, so it can be wired up to run your CSS through Sass first, then PostCSS after.

Ultimately I think I will shift from Sass to PostCSS, but for now this meant I could move a bit quicker by falling back on some of my trusty SCSS tricks to make organising and compiling my styles a little easier.

There are some gotchas though. One in particular is !important. You can use this with Tailwind rules to break the cascade where necessary, but Sass likes to pick up !important too and this can cause some conflicts.

So if you want to use !important with your Tailwind rules, you need to slap Sass on the wrists:

1.btn {
2 @apply bg-blue #{"!important"};

This (#{"!important"}) is Sass string interpolation, making the Sass interpreter just output !important so that it doesn’t try to do something that it can’t and allowing Tailwind to pick it up in a second or two.

I haven’t come across any other instances of this sort of conflict yet, so your mileage may vary depending on your tooling.

Basically, wherever there’s something in your stylesheet that might get picked up by both tools, you need to think a bit about the order in which it will get processed and you may need to apply some adjustments accordingly.

Alternatively, just forget about the preprocessor and embrace PostCSS.

Deployment strategies

When compiling assets from one place into another, (in this case from /site to /public), you do end up with two copies of some things — in some cases identical copies.

If you’ve not experienced this before, there are a couple of ways to deal with it for deployment:

  1. Commit the compiled files to your version control repository So you end up with some redundant files in your repository… at least you have the assurance of knowing that what’s in your repo is what will be deployed.
    This is a totally acceptable strategy in my opinion, but it does have some drawbacks: If you’re working on a larger team, it can create some friction and confusion by creating potentially more merge issues than you want to deal with. There is also a risk that someone overwrites a production-ready compiled file with a development version.
  2. Or; Compile the assets at deployment time This is by far my preferred approach. For sure, it takes a little more setting up — you’ll need Node & NPM on your servers — and it’s slightly riskier (Failed builds. Compiled versions in production don’t match development. This stuff happens.) but you have a cleaner repo, fewer merge issues with co-workers, and less chance of a development version making it’s way into production.
    Still one key drawback to be aware of: sometimes NPM goes down and suddenly your production server can’t install/update a package it needs to compile the latest assets. There are ways to mitigate this, but it’s just a bit more complicated than sticking everything in the repo.

If you can, go with option 2. I especially recommend it if you’re not flying solo on a project as you’ll benefit a little more from something I’ll call automatic contextual compilation: when you’re working locally, you compile development assets, which can make debugging easier; when you deploy to production, the production compilation steps run giving you leaner compiled assets. More automation = less thinking, which is generally good when it comes to well-trodden, repetitive tasks.

Plus, the less friction you have during code push and pull, the better it will be for everyone.

TIP: I like to have npm run watch running locally pretty much all of the time when I’m working on a theme. That way I don’t need to even think about running the build step most of the time.

Bonus round!

In case you’ve only been reading this post and haven’t skipped ahead to the code already, I’ve got a little treat for you.

It’s no secret that your final CSS when using Tailwind might be a little bigger than if you’d used other CSS frameworks. There are a number of ways to reduce the footprint of your stylesheet, but I wanted to skip straight to the best one and a great way of doing that in Statamic.

PurgeCSS + Tailwind + Statamic = 😍

If you choose to use Tailwind in your Statamic theme, you’ve made one of the best decisions you can this year. This combination makes it extremely easy to reduce your CSS footprint down to a mere fraction of its original size.

For example, the project I’ve been working on has a final (un-minified) CSS of over 400kb. That’s some heft. We could never go to production with that.

After employing the tricks I’m going to show you now, we brought that down to under 50kb! 🙀

It’s so small that we can keep our entire site’s CSS in just one file — just 1 HTTP request. And, thanks to browser caching, we don’t need to worry about code splitting or any other strategies just yet.

Here’s how we did it, showing the relevant parts from our webpack.mix.js:

1let glob = require("glob-all");
2let PurgecssPlugin = require("purgecss-webpack-plugin");
4class TailwindExtractor {
5 static extract(content) {
6 return content.match(/[A-z0-9_\-\:\/\\]+/g) || [];
7 }
10if (process.env.NODE_ENV == 'production') {
11 mix.webpackConfig({
12 plugins: [
13 new PurgecssPlugin({
14 paths: glob.sync([
15 path.join(__dirname, "js/**/*.js"),
16 path.join(__dirname, "layouts/**/*.html"),
17 path.join(__dirname, "templates/**/*.html"),
18 path.join(__dirname, "partials/**/*.html"),
19 path.join(__dirname, path_to_root, "site/content/pages/**/*.md"),
20 path.join(__dirname, path_to_root, "site/addons/*/resources/views/**/*.blade.php"),
21 ]),
22 extractors: [
23 {
24 extractor: TailwindExtractor,
25 extensions: [ "html", "js", "php", "vue", "md" ]
26 }
27 ]
28 })
29 ]
30 });

As you can see, this is a bit of a twist on the PurgeCSS config that Tailwind recommends. We’ve explicitly added a bunch of Statamic paths into the mix to make sure that all of the places that might reference class names in our CSS are inspected.

One of the great benefits from doing this (as you can see, only when NODE_ENV=production) is that during development you get the complete CSS, which gives you complete flexibility when coding up templates — create a style, use it in a template, compile the CSS, refresh the page.

Then in production, PurgeCSS will run and pull out all of the CSS that we’re not using.

Add a minification step and you could end up with a super tiny style sheet!

⚠️ Heads up ⚠️

This isn’t perfect! It will occasionally suffer from false-positives, resulting in some rules remaining in the compiled stylesheet that you’re not actually using anywhere. That’s an unavoidable side-effect of the way the rule-matching process works. Mostly the impact of this will be minimal, so it’s fine, but worth knowing about.

More importantly, if you reference class names dynamically in your Statamic content (i.e. you have a field for “Custom CSS classes” or similar), you may need a way to run your CSS compilation on a regular schedule or on-demand from the Statamic CP to make sure those new classes that are referenced have been included. How you tackle this will depend on your specific requirements and deployment procedures.

In conclusion

For me, Tailwind has been revolutionary. It’s completely changed the way I think about CSS. In fact, on this project, I hardly wrote any real style rules of my own! Amazing.

Statamic too has changed my mind about CMSs. Coming from the likes of WordPress, ExpressionEngine and other similar solutions, having a flat-file CMS is a dream. Everything’s in Git, everything’s super quick. But to top all of that, the actual management side, the Control Panel, is a pleasure to use and craft into a genuinely great experience for site admins and content editors.

With this flat file approach, Tailwind’s flexibility, Laravel under the hood, and the Laravel community and ecosystem building on top of the best tools available to web developers, we now have a perfect storm for building incredible web experiences backed by a client-ready CMS and to do that rapidly without cutting corners or wrestling against systems built with total backwards-compatibility in mind (I’m looking at you, WordPress!)

Without all of these, I don’t think we would have been able to get the smallest CSS I’ve ever built for a whole website with just 2 developers against the clock.

I really hope that I’ve convinced you to try these brilliant tools. At the very least that I’ve given you a few ideas to apply to your own projects in the future. Thanks for reading!

“It was worth it if you learned something from it”
— Merlin

#notadesigner • #sometimesitworks

All content licensed CC BY-SA 4.0  •  Code highlighting by Torchlight