Tailwind: when and how to use it
By Paqui Calabria, Frontend Engineer
In November 2020, TailwindCSS released a second version with an eye catching promo video and the internet (aka the people I follow on Twitter) went wild. It triggered the usual web debate: “What is this? Can I use it for my project?”, “It’s the best thing ever!” ,“What are you talking about? Hide that monstrosity!”
I was already using Tailwind and still use it today, so I’d like to share my experience in this post. I’ll take you through the different uses of Tailwind, the benefits and downsides, and then I’ll share my recommendations and tips.
You may already know CSS libraries like Bulma, Material or the omnipresent Bootstrap. So you’re likely wondering — why use another one? Tailwind is actually not a styles library; it’s a tool that simplifies the process of creating your own CSS library. It comes with some defaults, but those can be adapted. It’s user-friendly, so as long as you use it well, your CSS files won’t grow to infinity (and beyond) and will stay in place. You may wonder what kind of dark magic this is… And if you promise you won’t run away, I’ll tell you: utility classes.
Tailwind is a tool built on top of PostCSS that uses utility classes, so you can create your CSS library quickly and easily.
Since I started working with Tailwind, I’ve used it in many situations, both personal and professional. I’ve found two use cases where it really shines: very small projects (like proofs of concept or personal experiments) and projects with a complete style guide for the dev team to follow. Let’s deep dive into those two situations.
Still have some doubts about whether you should use Tailwind? Then keep reading!
Tailwind for small projects
If you’re working on a small, simple project or proof of concept (POC) with little design involved, this is a great opportunity to use Tailwind. You can select a few colours from Tailwind’s palette (it’s super complete in v2!) or you can modify the default theme to customise things like the font and colours. There’s no need to overcomplicate it — just remember to add spacing between elements and set a background colour for buttons.
Your code, however, will not be high quality, which is why I only recommend this for POCs or projects you want to code in three days and never look at again!
Check out the difference between the result and the code:
<div className="flex flex-wrap md:flex-no-wrap px-3 py-4
odd:bg-teal-300">
<div className="w-20 sm:w-32 lg:w-40 mr-2
flex-grow-0 flex-shrink-0 bg-pink-200"><img
src={imageId
?
`http://covers.openlibrary.org/b/isbn/${imageId}-M.jpg?default=false`
: '<https://dummyimage.com/130x170/fed7e2/fed7e2.png>' }
alt=""
onError={onImageError}/>
</div>
<div className="flex flex-1 flex-wrap content-center mr-2" data-test="bookInfoWrapper">
<span className="w-full text-xl
font-medium">{props.title}</span>
<span className="italic text-gray-700">{props.author}</span
<span
className="text-gray-700"> - {props.year}</span>
</div>
{
props.hasSubject &&
<button className="self-center w-full md:w-auto flex-none p-2 mt-3 md:mt-0 ml-auto shadow-lg rounded-lg border-2
border-pink-700 text-pink-700 hover:bg-teal-100 active:bg-teal-100 focus:bg-teal-100"
onClick={props.onSearchSimilar}>{language.similar}</button>
}
</div>
JSX doesn’t help and the accumulation of classes is painful. Try to imagine a full app written like this! It’s not only hard to debug the styles, it also makes it hard to understand the rest of the code. Will you ever debug this kind of project? Extremely unlikely. That’s why I use Tailwind for “weekend experiments” without remorse.
Another thing I love about Tailwind is the development speed I can achieve when there’s no need to think about class names or whether something should be 1px bigger or smaller. I also love having a set of sensible defaults, a few options for each property (not infinite), and not having to go back and forth between CSS and HTML files.
“But you can have the same advantages with any CSS library.” I would argue that you can’t at this level. Tailwind classes are easier to remember than in other libraries (since they are named after the style rather than the component) and they’re not linked to the HTML. It’s also much more flexible: do you want to use rounded corners? Go for it. Do you want something with a Commodore style? No problem! Three colours plus white or a complex range of shades? You choose! You can do everything with zero effort.
Have you ever used a CSS library just to find out that a component you need (let’s say, a form group) isn’t available? So you end up doing it by yourself, reverse-engineering the styles so it looks similar? This won’t happen with Tailwind, since there are no components to begin with.
And don’t get me started on how some libraries pollute the HTML because you have to keep adding classes to get the desired configuration. Does this sound familiar: class=”btn btn-secondary btn-lg btn-block”? Another great thing about Tailwind is that you’re not tied to a specific HTML structure (sometimes following bad patterns) as it’s only for styling.
Tailwind for style guides
Now let’s focus on a second area where Tailwind shines: style guides. To make spaghetti classes work in the long term (and stop hurting our poor frontends’ hearts) we need components. There are two ways to do it:
- Functional components: using your framework of choice, just hide the messy classes with any other needed behaviour. Here’s an example:
<div class="container">
<h2>Error</h2>
<p v-if="errorMessage" class="text-red">{{errorMessage}}</p>
<div v-if="error403" class="text-gray-700">
<p class="mb-2">We don't recognize you. If you still think you should be able to access this site, try logging in:</p>
<a
:href="loginUrl"
class="mb-2 btn bg-primary text-white hover:bg-primary-700 hover:text-white focus-active:bg-primary-700
focus-active:text-white"
>Okta login</a>
</div>
<p class="mt-6">Problems? Suggestions? Contact us:</p>
<div class="space-x-4">
<a class="inline-flex flex-col items-center"
href="<https://slack.com/intl/es-es>">
<font-awesome-icon :icon="['fab', 'slack']" size="3x" />On Slack
</a>
<a class="inline-flex flex-col items-center"
href="<https://www.atlassian.com/es/software/jira>">
<font-awesome-icon :icon="['fab', 'jira']" size="3x" />On Jira
</a>
</div>
</div>
- CSS components: these are probably more similar to what you’re used to. Just select a class name and compose it using @apply. Here’s an example:
.btn-primary {
@apply btn bg-primary text-white;
}
.btn-primary:hover, .btn-primary:active, .btn-primary:focus {
@apply bg-primary-700;
}
.btn-primary:disabled {
@apply bg-primary-400;
}<button v-on:click="prepareEdit()" class="btn-primary">
<font-awesome-icon icon="plus" class="mr-2"></font-awesome-icon
<span>Add user</span>
</button>
This can be done with plugins too (which has more benefits). But more on that later!
This second option looks way better, right? It does, but it creates a big problem because it goes against one of the main benefits of Tailwind: stable size. If you just use the utility classes, your CSS doesn’t grow because you’re just using the same classes over and over again. But when you start declaring components in your CSS, the @apply takes the values from the configuration and copy-pastes them, so your CSS file will start growing.
On the other hand, CSS components present one benefit over functional components: they can be integrated into Tailwind configuration through plugins. See that @apply btn? It’s not a Tailwind class: .btn is declared on the plugin I’m using (with all the other style guide definitions).
So which solution is best to use? In my experience, what works best is a mix of both:
- Customise the values in the configuration to match your style guide i.e. colours, sizes, breakpoints, etc. Consider any value that cannot be set with an @apply as a magic number: you should have a good reason to use it and only do it only once. This will keep your styles consistent and easy to refactor if needed.
- Create micro components within the configuration using the addComponent function and use the theme values on them. You can create as many as you need, but don’t create too many. Try to avoid writing components that rely on a specific HTML structure — that way it’ll become a tool that makes development easier, rather than one that forces you to check docs or source code every time you want to add a dropdown.
- Compile all of this into a plugin and make it easy to access and install.
- Create bigger components (like accordions, for example) as a “functional component”. They might not look beautiful, but think of it as an incentive to componentizice your app, which is a good thing anyway (separation of concerns, small pieces, easier to test and debug, etc).
If done correctly, you should have a lot of small components with a high number of classes, but since they’re small it should still be manageable:
<div class="bg-white rounded border border-gray-400 p-5 float-left w-full">
<h3 v-if="title" class="font-bold mt-0">{{title}}</h3>
<div ref="cardContent">
<slot/>
</div>
<button v-if="copy" class="btn bg-primary text-white
hover:bg-primary-600 active:bg-primary-700 focus:bg-primary-700 mt-5 float-right" v-on:click="copyToClipboard" :aria-label="`Copy ${title} to clipboard`">
Copy to clipboard
</button>
</div>
And then pages or higher lever components that look like these:
<div class="flex flex-col overflow-hidden">
<mc-action-bar :title="currentEnvironment">
<template v-slot:navigation>
<router-link to="/marketplace/users">Users</router-link
<router-link
to="/marketplace/info">Settings</router-link>
</template>
</mc-action-bar>
<div class="container pt-6 flex-shrink overflow-auto">
<router-view></router-view>
</div>
</div><div class="flex-shrink overflow-hidden flex flex-col">
<div class="bg-white rounded border border-gray-400 p-5">
<div class="text-xl mb-2">Search for users</div>
<form v-on:submit.prevent="searchUsers">
<div class="flex items-center w-full">
<input v-model="userIdSearchBox" class="flex-grow
h-7 pr-2 input-group-text" placeholder="User ID" type="text"/
<button :disabled="!userIdSearchBox" type="submit" class="btn-primary m-1">Search</button>
</div>
</form>
</div>
<div class="space-y-5">
<card title="Site_key" :copy="true">
<p class="m-0">{{marketplaceInfo.siteKey}}</p>
</card>
<card title="Site_secret" :copy="true">
<p class="m-0">{{marketplaceInfo.siteSecret}}</p>
</card>
</div>
</div>
To prevent everyone in your team developing their own accordion, you could make a bigger components library compatible with the tool of your choice at your company (vanilla JS, react, vue, laravel…)
Once you’re using your shiny new plugin, you may notice that one of the projects incorporates elements that are not on the style guide but shouldn’t be created as a functional component i.e. tables. In this case, you could extend the configuration or just use @apply to style them, as shown above. I try to do this by styling the HTML elements or using very simple class names (.table). If I have to think about what name to use in order to tell a component apart from the others, I take it as a sign that I’m creating too many components this way.
There is one downside to this: if you start composing your CSS components with a mix of config-defined components and utility classes, you may need to check the source code to know where the property in question is coming from.
Remember this code?
.btn-primary {
@apply btn bg-primary text-white;
}
This is what you see on the dev tools:
As you can guess, the padding, border-radius and sizes are applied by the btn component. This isn’t a big deal as long as you don’t “nest” by more than one level.
So…will Tailwind solve all my problems?
The short answer is no. It will solve some of them, but there are others that require time and experience on your side. Without someone with solid CSS knowledge in your team, your code could still be buggy and messy. Without someone who knows the difference between margin and padding, making them .m-2 and .p-2 could become even more confusing.
If you don’t have a style guide that is followed consistently, you might end up spending more time modifying your Tailwind config or adding custom CSS to work around it than writing code, which can be slow, painful and frustrating.
If your style guide is simply composed of a few colours and one font family, an easier solution could be to simply use custom properties.
Final thoughts
As you can probably infer from this blog post, I love Tailwind for some cases, but I wouldn’t recommend it for others. I think it does a few things very well (keeping the CSS files short and increasing the development speed are my favorites) but there’s a lot of room for improvement (they could use custom properties now that support for Internet Explorer is dropped, for example).
As with any tool, you need to know what you’re doing in order to use it correctly. Be wary of anyone who tells you that Tailwind is a silver bullet. Actually, be wary of anyone who believes there are silver bullets!
If you want to know more about the tool, I highly recommend looking at their website. You might also want to read these articles presenting opposite points of view: why Tailwind was created and the main pain points. I’d take them with a pinch of salt because they’re both based on personal preferences, but they’ll still give you a good overview.