Tailwind CSS is a utility-first CSS framework for rapidly building custom user interfaces. Instead of predefined components, it offers low-level utility classes that can be combined to create unique designs directly in your markup.
I was very skeptical of Tailwind initially. However, when starting Angry Building, I wanted to write as little CSS and JavaScript as possible, so I decided to try Tailwind.
Over time, I changed my opinion of it. It fits quite nicely with my component-driven approach to UI.
Today, I’m going to cover tips about Tailwind CSS:
So, let's get started 👉
🧹 Use ESLint plugin
When used effectively, Tailwind CSS can significantly enhance project structure. One of the initial tools I integrated with Tailwind in my projects was the ESLint plugin for Tailwind - eslint-plugin-tailwindcss.
Its biggest feature is that it validates class names because making a typo with the Tailwind class names is too easy. Before this plugin, I used a custom TypeScript checker, which worked well but was clunky.
A couple of other cool features are:
enforces you to merge multiple classes into one
enforces consistent class name order
can forbid the usage of arbitrary values
avoiding contradicting class names
For VSCode users, there is an official VSCode plugin as well.
🔎 Search documentation with Alfred / Dash
I use the Dash macOS app for offline documentation and Alfred for application launcher.
There is a plugin that integrates Alfred and Dash that works wonderfully for Tailwind.
It is especially useful when you need to discover if Tailwind supports some CSS properties and what the variants around them are.
🚫 Don't use Tailwind without a component system
To avoid messy code and improve maintainability, always pair Tailwind with a component system.
I don’t mean a design system but a component system like ViewComponent or React. Something that allows you to have reusable components.
Tailwind utility classes are applied only to a single HTML element; if you need to reuse it, you have to copy-paste the code. Then, if you decide to change it, it gets messy.
A component system is a must when using Tailwind.
I feel a lot of backlash against Tailwind because people use it as a simple CSS replacement without a component system.
📦 How to parameterize components
I found Tailwind to shine when using a proper component system. Most of your complex and log Tailwind classes are encapsulated in components in this setup. Then, you can use the flex and spacing utils to arrange those components and handle edge cases.
When we use UI components, we need to be able to configure them.
Allowing arbitrary class names to be passed around seems very easy.
This approach can lead to styling conflicts and future issues because the component must know how it’s configured. I also need help controlling which styles get overwritten. Users won’t know what is written without checking the component's implementation.
Here are a couple of techniques I use to avoid allowing class names to be passed on to components.
Targeted props
Variants
Spacing
I'm going to use React as an example. I used those techniques with the ViewComponent library as well.
🎯 Targeted props
Here is an example. I have this “Text” component, and I want its font size to be adjustable. The naive approach could be the following:
<Text className="text-sm" />
However, I can’t guarantee that this will be used only for font size or restrict what sizes can be used.
Plus, how will another developer know what classes to use?
I can technically restrict TypeScript to allow only “text-sm” / “text-lg” class names. However, this will break developer expectations, and someone will overwrite when we need another configuration.
A better approach is to have a separate "targeted" property that only deals with the size.
<Text />
<Text size="sm" />
<Text size="lg" />
We can use an object map to define all allowed props. You can even apply different font weights depending on font size.
Here is the implementation:
const SIZES = {
"sm": "text-sm"
"base": "text-base"
"lg": "text-lg font-semibold"
}
type IProps = {
size?: keyof typeof SIZES
}
function Text({ size: "base" }): IProps {
// ...
}
🎨 Variants
"Variants" is an extension of the target props pattern. Instead of targeting a single property, it allows your component to have more distinct variants.
Implementation-wise, it is the same as the targeted props—an object map with variant and class names.
I have seen two ways to this
As property
<Button variant="primary">
<Button variant="secondary">
<Button variant="small">
As component
<Button.Primary>
<Button.Secondary>
<Button.Small>
I prefer the component approach because it allows me to support different props for different variants. Some button variants support icons, and some might not.
Some components might have both variants and targeted props.
📏 Margins
The other job of having a "className" passed to the component is to set its "margin" relative to some other element on the page. However, it isn't a component's responsibility to know how it is positioned relative to another component; it is the job of the parent component to orchestrate this.
My approach to this issue is to shift from directly passing "margin" to components. Instead, I advocate for setting properties like “gap” and “padding” to their parent components or wrapping them in another component.
This ensures a more efficient and manageable way of handling margins and positioning in web development.
Here us an example:
// Bad
<div>
<MyComponent />
<MyComponent className="ml-2" />
</div>
// Slightly better
<div>
<MyComponent />
<div className="pl-2">
<MyComponent />
</div>
</div>
// Better
<div className="flex gap-2">
<MyComponent />
<MyComponent />
</div>
Max Stoiber has a good article about this subject.
✍️ Writing custom CSS
interesting CSS.Even though I use Tailwind, I still write CSS for my application.
Here are my rules for when to write CSS:
What I want doesn’t have a Tailwind equivalent. Some fancy grid setups and things are dependent on CSS variables.
Tailwind classes get too interconnected and hard to reason about. Usually involving styling multiple child components. When the “Cascading” part of CSS is needed.
Automatic style. Style is applied when a data attribute is applied to an HTML element.
I want to reuse a class name, not a whole component. An example in Angry Building is the “c-input” class name, which is applied to “input,” “select,” and “textarea,” which are wrapped by different components.
My custom css is
In React projects, I use the CSS module in the same folder as the component.
In Ruby on Rails projects, prefix all classes with “c-”, like “c-input” or “c-button-primary”.
I rely on @apply, a directive to reuse Tailwind classes in my custom classes. In this way, I don’t have to remember how much is “gap-2” or “mt-3” or what is in “text-sm”
I don’t have much custom CSS.
💡 Cool tricks
Tailwind is full of cool tricks. Every time I watch someone writing Tailwind, I learn a lot. One such video is this talk by Adam Wathan (the creator of Tailwind) at Rails World 2023.
One of my favorite Taildwind features is the "group," which allows you to change the child's style based on the parent's actions. This has removed a lot of JS code.
<div class="group">
<span class="group-hover:bg-cloud">
When parent element (group) is hovered, I change background
</span>
<span>
I don't change color
</span>
</div>
The “grid” utilizes made the CSS grids a lot more accessible to me.
<div class="grid grid-rows-3 grid-flow-col gap-4">
<div class="row-span-3 ...">01</div>
<div class="col-span-2 ...">02</div>
<div class="row-span-2 col-span-2 ...">03</div>
</div>
💨 Tailwind UI
Tailwind UI is the official Tailwind CSS components and templates project. It is created and maintained by the team behind Tailwind CSS.
These are not only great components that can be copied and pasted into a project, but they are also a great learning resource on how to write good Tailwind.
Conclusion
I have now used Tailwind in Ruby on Rails / ViewComponent and React projects, and I consider Tailwind one of my default tools.
I still write CSS; however, because of Tailwind, I now only write interesting CSS.
What I like about Tailwind is
It makes it easy to prototype UIs
I can copy Tailwind UI components directly and start iterating on them
It keeps my CSS bundle small, and I have to have a single CSS file for my whole project
It taught me to think mobile-first
It forces consistency in terms of colors and spacing.
If you have any tips, you strongly agree or disagree with them or have ideas for something I missed, уou can ping me on Threads, LinkedIn, Mastodon, Twitter, or just leave a comment below 📭
Awesome as always! I can only add the following:
Tailwind and similar frameworks are great :) but we must not forget the "C" in CSS, which stands for "Cascading". The combination of utility classes + custom CSS from the perspective of "Cascading" is something like a silver bullet (even though such a thing doesn't exist).