Getting Started With TailwindCSS

Tailwind is a utility-based css framework that inverts a lot of assumptions about how CSS classes should work, and forms a very viable alternative to something like Bootstrap. So let’s take a deeper look.

I’ve been using beta versions of Tailwind on a few projects of various scales for a couple of years. But version 1.0 finally came out recently and I thought it was worth talking about it.

This article isn’t intended to teach you Tailwind. It’s intended to discuss Tailwind in general terms, what it’s for, and why you’d use it instead of another alternative.

So What Is Tailwind?

Tailwind is a utility-based CSS framework, intended to facilitate rapid development of modern web applications. The key is that it’s utility based. Rather than one preset “big” class that sets a bunch of properties you will add multiple classes, each of which set (approximately) one.

Unlike other frameworks Tailwind uses PostCSS to process its final output, a choice with some downsides as well as up.

How does it differ from Bootstrap?

Just as an aside here, when I talk about “Bootstrap” you can consider that shorthand for a bunch of competing libraries — Bootstrap, Foundation, Bulma, and more.

As said, Tailwind is utilities, so you end up with a long list of deliberate and considered properties being set.

This means that rather than starting with an interface that looks like a default Bootstrap install, and then having to customise it, you end up with something uniquely custom.

The downside — you start from nothing. Bootstrap gives you a lot out of the box. A lot of pre-styled components that already look good. Form elements that line up, nice buttons, table layouts, navigation bars, notifications, cards, a grid, crisp typography, and so on.

You get none of that with TailwindCSS. You either get to make your own, or you’re forced to make your own, depending on your perspective.

And let me say from the outset that Tailwind isn’t always the right choice. If you’re whipping up a really basic interface for internal use or a quick proof of concept, Bootstrap is your go-to.

Where Tailwind excels is longer-lived projects that require their own visual style and identity. This is particularly relevant when implementing custom design work and especially the kinds of slick, modern app interfaces that stray pretty widely from Bootstrap.

Getting Started With Tailwind — The Wrong Way

Like other CSS frameworks there’s an easy way and a right way. To look at and have a quick poke at Tailwind’s features, the wrong way is perfectly fine for now.

I’m going to be doing all this with Parcel, and with good reason. It’s ideal for this kind of quick test application.

We’ll start up a quick app with some minimal functionality much as done in the article on Parcel.

mkdir tailwind-demo
cd tailwind-demo
touch index.html
code .

Note that the last line there just opens the directory in Visual Studio Code, so your command may vary. So then in Visual Studio we can just run parcel index.html and it will all work nicely.

So the first thing we’re going to do is edit our file and make a base HTML structure with minimum markup and add a link to a CDN.

<!DOCTYPE html>
<html>
<head>
<title>Tailwind</title>
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>

<h1>Tailwind Demo</h1>
<p>This is a tailwind demo.</p>
</body>
</html>

Obviously this isn’t going to do much but let’s see how it looks when we look at parcel’s output.

Wow. That’s pretty bad. In fact, it’s worse than it would be if you had no CSS at all — Tailwind adds some reset code that removes default H1 sizing, etc.

Let’s start with colour

Tailwind is a utility-based framework. That means it starts with no components or assumptions. There’s no such thing as a “button” class.

If we want things to happen we have to set them as classes. Colours are a good example. Let’s make our h1 tag blue.

<h1 class="text-blue-500">Tailwind Demo</h1>

Ok, so the number there might not be clear. It’s also kind of new to me, Tailwind didn’t work like this for the beta. Essentially the number is the darkness. 100 is the lightest, through to 900 as the darkest. Increments of 100 are supported, but let’s skip a few.

<h1 class="text-blue-100">Tailwind Demo</h1>
<h1 class="text-blue-300">Tailwind Demo</h1>
<h1 class="text-blue-500">Tailwind Demo</h1>
<h1 class="text-blue-700">Tailwind Demo</h1>
<h1 class="text-blue-900">Tailwind Demo</h1>

We want to give the site a background colour, so let’s mess with colours just a bit more.

<body class="bg-blue-100">
<h1 class="text-blue-500">Tailwind Demo</h1>
<p class="text-blue-900">This is a tailwind demo.</p>
</body>

Colours in Tailwind — They’re not all available

There’s a saying that constraints breed creativity. By limiting your palette to this defined set of colours you get a cohesive visual style that is easy to work with. I worked on a site once that had approximately 2,700 different colours defined in its stylesheet. This was in part because of use of things like lighten() and darken() which multiplied out already piecemeal colour choices for shadows, etc.

Tailwind helps avoid that by giving you only pre-defined colours that work together. It can also be extended, but we’ll go into that later.

Here is the full list of standard colours.

These colours work with lots of elements- text, backgrounds, borders, etc. They’re also nice to use together or in place of things like black and white. Where a button might be green with white text, for example, it can instead be green with lightest-possible-green text, providing a nicely complementing colour scheme. Similarly, lightest possible colour variants can be a nice replacement for background colours, and if a neutral colour is needed, lightest grey is a better option than white a lot of the time.

Margins and spacing

So this is heading in the right direction now. But there’s more to life than colours. We also have it crammed into the top left, and the H1 isn’t any bigger than the paragraph text.

<body class="bg-blue-100 m-5">
<h1 class="text-blue-500 mb-3 text-xl">Tailwind Demo</h1>
<p class="text-blue-900">This is a tailwind demo.</p>
</body>

Here we’ve got some text size set on the heading, and also used a margin property. The margin can be set as I’ve done by using the numbers 0–20. These aren’t any specific unit, they’re just sort of relative. Be aware that the number sequence isn’t complete. 0 to 10 is only missing 7 and 9, but over that are big gaps with only 12, 16 and 20 supported. Note that you can use m{location}-{number}, and that location can be a number of different things. Omitting it entirely makes it all directions, but my-5 and mx-5 will give you vertical and horizontal space respectively. Other options are ml- and mr-, mt- and mb-, which do what you’d expect. Interestingly you can also put negative margins with the slightly less intuitive syntax of -mt-5 for a negative top margin, for example.

All of the above also applies to p as well, with pb-3 applying a smallish bottom padding.

In any case, you can see from this how our iterative approach is getting us slowly closer and closer to the design.

Putting the pieces together

Let’s make a button. We’ll just make a Bootstrap copy.

<button class="py-2 px-4 rounded bg-green-600 text-white">
Success
</button>

The one on the left is a Bootstrap official success button, the button on the right is my copy. It’s hard to duplicate exactly. Tailwind’s colours are slightly less saturated than the Bootstrap rainbow candy wonderland, and without having more granular padding this was the closest I could get. Using py-1 was significantly thinner.

Let’s talk about what I’ve done there. I’ve got a padding of 2 vertically and twice as much horizontally. (This is typical in buttons, we see horizontal and vertical space differently.) We then set rounded which gives the corners a rounded appearance. There is also rounded-sm which is more understated, and rounded-lg which is some Fisher-Price bullshit. Actually rounded-sm looks the best. They don’t look at all round, just kind of… softened.

We’ve defined the text as white, obviously.

Ok, but even if you know all of these classes you would have to admit that the Tailwind version is crazy verbose for roughly the same outcome. In fact the Tailwind version is doing even less — it has no mouse hover state.

Hover States

Thankfully Tailwind supports these hovers easily. All we have to do is add hover:bg-green-700 hover:text-green-100 and it will change on hover like Bootstrap. To be honest I don’t think the Bootstrap one changes text colour, I just wanted to demonstrate how you can do two things, or even invert colours.

But now it’s even longer.

The point that we’re missing here is that we’re trying to recreate Bootstrap’s buttons. Which is most easily done by… using Bootstrap. Let’s make our own buttons instead.

<button class="py-2 w-64 px-6 bg-indigo-600 text-indigo-100 
text-right font-hairline mr-5 border-indigo-800 border-r-8
hover:border-indigo-900">
Next section &raquo;
</button>

If you’ve ever tried to extensively customise Bootstrap you’ll know it’s very easy to miss a state or situation. Like, you’ve fixed the normal and hover state so that now it’s lavender, but it still turns orange when you click it. Or on mobile. Or when it’s a block element. Or whatever. It’s also easy to find certain bits (such as borders) surprisingly difficult to manipulate. The point is our element here only has the styles we gave it. No surprises. We have a completely custom visual style.

But… goddamn that’s a lot of classes.

The first thing to say about that is that’s probably ok. I mean, this is a bit of an extreme example, but in a more typical element there is nothing wrong with having three or four utility classes on there.

But in this case, we’ve kind of reached a point on our button where… we like it. This is good enough. More particularly we want to be able to re-use this style and not accidentally miss a bit and have a just-slightly-different button. Or if we want to tweak it, it might be useful to do that in one place.

We can do that by extracting our classes. But to do that, we need to put on the big boy pants.

Installing Tailwind — The Hard Way

Ok, this really isn’t rocket surgery. Tailwind is not too hard to use the hard way. Tailwind doesn’t use a typical less or scss pre-processor, but actually a post-processor called PostCSS. Thankfully Parcel itself does all the heavy lifting.

You’ll find quite a lot of different things online about how to get tailwind working with Parcel. Many are wrong. A lot do unnecessary steps because it’s not clear what’s mandatory and what’s not. Most are out of date. At time of writing this is all you need to do to get Parcel and Tailwind working together.

Add a file called postcss.config.js and add the following code and save it.

module.exports = {
plugins: [
require('tailwindcss')
]
}

Parcel (which already uses and understands PostCSS for some of its own internals) sees the required module and downloads and installs it. You should note that you now have a node_modules directory.

Make an index.css file, and then drop this in it.

@tailwind base;
@tailwind components;
@tailwind utilities;

Replace your index.html CDN link with this one, and we should be back to where we want to be.

<link rel="stylesheet" type="text/css" href="./style.css">

Ok, at this point we have Tailwind and Parcel playing nicely together. There are a bunch of things you don’t need to do that a lot of instructions seem to think you do. You don’t need to init anything. You don’t need to use the Tailwind command-line for anything. You don’t need a tailwind.js that contains the base css.

The reason the instructions for this aren’t particularly clear is that Tailwind doesn’t give details on setup with Parcel, and Parcel doesn’t give details on setup of Tailwind, just PostCSS itself, so there’s a bit of figuring out in the space between the two.

Anyway, moving on.

The goal now is to extract that button style and make that whole block a single class. You do this with the @apply keyword.

.button {
@apply py-2 w-64 px-6 bg-indigo-600 text-indigo-100 text-right font-hairline mt-10 border-indigo-800 border-r-8;
}
.button:hover {
@apply border-indigo-900;
}

This tells PostCSS that when it sees the .button class it should apply all of the following classes. So now we have all the ease-of-use of a Bootstrap button class, but a completely unique visual style. Note that you have to separate out the hover state, you can’t apply that all in one go.

There is a downside to all those classes though. What if we want a red button? Or a green one? The solution to that is to extract smaller sets of classes and then use them together.

.button {
@apply py-2 w-64 px-6 text-right font-hairline mt-10 border-r-8;
}
.button-indigo {
@apply bg-indigo-600 text-indigo-100 border-indigo-800;
}
.button-indigo:hover {
@apply border-indigo-900;
}
.button-green {
@apply bg-green-600 text-green-100 border-green-800;
}
.button-green:hover {
@apply border-green-900;
}

There is also nothing wrong with combining extracted classes with overrides, and it’s better to not include too much in the class. For example, in our code here we have mt-10, meaning our buttons must always have a fairly big margin above them. We probably don’t want that actually in our button class, when we can just as easily say <div class="button mt-10"> and not be required to always have it.

Weird Flex, But Ok

A Bootstrap-style 12 column grid is a bit naff these days and naturally Tailwind supports flexbox layouts.

Bear in mind, though, that Tailwind is not an abstraction. It’s just utility classes implementing the underlying flex properties. If you don’t understand flex you won’t be any better off.

(If you don’t really get flex, there’s a great set of exercises called Flexbox Froggy as a game. Highly recommended.)

In any case, here’s a super simple layout. It’s got a left nav taking up 20% of the space — deliberately chosen because 1/5th is really hard to do in Bootstrap — and a body taking up the rest. The nav and body content are also the same height.

The code to do this is reasonably compact. A lot of what you’re seeing here is just me making it inexplicably orange for contrast.

<div class="flex bg-orange-300 p-5">
<div class="w-1/5 bg-orange-700 text-orange-100 p-5">
LeftNav
</div>
<div class="flex-1 bg-orange-400 text-orange-800 p-5"> <h2 class="text-orange-700 text-lg mb-5">Tailwind Layout</h2> <p class="mb-3">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius natus iure suscipit repellendus quis nostrum, debitis repudiandae, laudantium fuga quas voluptatum illum, expedita eum sint ratione vero sunt! Id, sunt!</p> <p class="mb-3">Deserunt nihil quo numquam ut eaque voluptates rem, eveniet explicabo culpa fuga dolorum amet fugit eos! Enim sint dolorum inventore at perferendis.</p>

</div>
</div>

Responsive Design

Tailwind is built as a mobile-first framework, and that means the viewport is assumed to be mobile and breakpoints are hit as needed as the interface scales up. Not down. This means you should develop truly mobile first. Don’t make a desktop application and then try to make it work on mobile. Seriously. Don’t.

Let’s look at what that looks like in practice.

We can take the above design and make it mobile friendly by… ack. You can see now that I’ve fallen into the exact thing I’ve told you not to do. So let’s rebuild the thing above as a mobile version first. I don’t want it that different. I just want the “leftnav” to be at the top.

Magnificent, and done with the following markup, which is almost identical to before. Only the first two lines have changed.

- <div class="flex bg-orange-300 p-5">
- <div class="w-1/5 bg-orange-700 text-orange-100 p-5">
+ <div class="flex flex-col bg-orange-300 p-5">
+ <div class="flex-1 bg-orange-700 text-orange-100 p-5">

In order to make that responsive we need to pick a breakpoint at which we want it to do something different. The built-in points are roughly the same as Bootstrap.

It’s critically important that you bear in mind that your design is a mobile layout, and you’re setting what you want to change at a higher resolution. For a long time this felt “backwards” to me, but it really is true mobile-first. Anyway, this is what we end up with as our new and responsive two first lines.

<div class="flex flex-col md:flex-row bg-orange-300 p-5">
<div class="flex-1 md:flex-none md:w-1/5 lg:w-1/6 xl:w-1/8
bg-orange-700 text-orange-100 p-5">

By default the container will have flex-direction: row applied, but at the medium breakpoint will have the flex direction changed to column and the first element changed to no longer having a flex layout but instead a width of one fifth. At higher breakpoints it will shift steadily down to a proportionally narrower sidebar.

It’s funny seeing classes like md:w-1/5. It doesn’t look like that would be valid, but it is. There is a surprisingly broad range of allowed characters in class names.

There is also a useful class of container which makes a generic box that flicks down through a number of breakpoints, but remains at fixed sizes. To clarify, using <div class="container mx-auto"> gives a box that is a fixed with, and centred. When it goes down a breakpoint it drops down to a smaller size, but it doesn’t change size at all during those weird mid-breakpoint sizes. This makes it possible to design for a fixed set of sizes, rather than every size between. Strongly recommended.

Some General Advice

Tailwind works fine with frameworks like React. If you are using React, dealing with dynamic classes is a shit. There’s a simple but useful library unhelpfully called classnames that helps build your class lists, and is very useful for Tailwind.

It might be repetitive at this point, but start mobile-first. Assume your layout will be used on a mobile device and then add features for a design that has more real estate.

Don’t be too quick to extract custom classes. There’s often no real need or benefit. Put that sort of thing off until the design patterns have coalesced into something consistent and regular.

The hardest thing for me was building “components”. Where something like Bootstrap has extensively documented components like navbars and cards, for almost the entire of my use Tailwind has had a much more trial-and-error based system. Thankfully, Tailwind’s documentation now includes a components section that provides copy-and-pasteable code for how to do these kinds of elements in a minimal and effective way. Naturally they are only a starter, so feel free to get rid of shadows, change rounding, etc.

Conclusion

Tailwind isn’t going to be suitable for every project, and that’s fine. If you’re doing something quick and dirty where appearance doesn’t matter, or that’s time-limited, you’ll get more out of the pre-existing components for Bootstrap.

Anything more bespoke and long lasting is well worth doing with Tailwind. That said, Tailwind (or any framework really) is poorly suited to “pixel perfect” recreations of website designs. If your dev process is being given a Photoshop document from on high by a designer who nitpicks font sizes and exact pixel whitespace, then your dev process is broken anyway. Only Jesus can help you now.

There’s a long road of learning Tailwind. It’s not a particularly pleasant one. You have to abandon a lot of preconceptions about how classes can, do, and should work, what your markup should look like, and what is and is not elegant and efficient.

You have to accept that there are limitations and constraints. Constraints on colour, on margins and spacing, constraints on font sizing. You can embrace that and let it free you to focus on smashing out a layout that’s guided by the design and get on with solving real problems. Or you can fight it, fail, and hate it.

These issues aren’t unique to Tailwind, of course. Any CSS framework has constraints.

But certainly Tailwind is worth a look. You may well find it suits some of your projects. Certainly there are projects I’ve worked on that I didn’t use it that I really wish now I’d invested the time.

Senior Web Developer based in Bangkok, Thailand. Javascript, Web and Blockchain Developer.