This article is from a clip from the ConTejas Code Podcast. Watch the full episode on YouTube: Shruti Balasa: How to Maximize Succes...
Introduction
The guest in this episode is Shruti Balasa, an expert consultant and educator of tailwind CSS.
Tailwind CSS is a utility-first CSS framework that helps you write extremely fast CSS. What that means is that Tailwind is atomic. Meaning, with Tailwind you write one class name as a class attribute, equating to one CSS rule.
Tailwind CSS has a bunch of trade-offs and the guest in this episode captured the nuance of all these trade-offs in the “Deep Dive Discussion with IT expert Shruti Balasa”. It is a really fun deep dive and the host (Tejas) and guest (Shruti) talked about cases where Tailwind is good, cases where Tailwind isn’t helpful, some ways it can add value or detract value, and most importantly how it works on the inside using its just-in-time compiler and other tricks. This is a real treasure of information and I hope you enjoy it.
A big criticism that I’ve hard from CSS “haters” which I think they’re wrong, is that Tailwind CSS generates one class name per-rule. So like flex is only display: flex
, nothing more in that declaration. So some people who maybe aren’t familiar with the concept of atomic CSS for Tailwind or utility classes, they criticize Tailwind saying that it’s a massive CSS file in megabytes shipped to your users. What’s an appropriate response to that?
The response to that according to Shruti Balasa is: in larger projects, you are likely using flex on almost all your elements. Let’s say you have 100 elements where you’re applying flex classes. If that rule is defined in your stylesheet, it’s only generated once. Now, compare that to a scenario where you have 100 elements in your stylesheet, each with its own display flex rule written 100 times. So, how can you say one approach is okay, and the other isn’t?
Eventually, when you examine the entire project and its stylesheet, it kind of balances out. In fact, most projects end up with CSS files that are less than 10KB. Essentially, the styles generated aren't as you might think. Even if it's just one rule, you’re using that rule across multiple places, which ultimately helps reduce the size of the stylesheet itself.
Two Examples to demonstrate using Tailwind CSS
Using Tailwind CSS with utility classes – Flexbox is applied via Tailwind's utility class (flex). The class is defined once in the Tailwind stylesheet and applied to multiple elements.
Manually writing the same display: flex rule for each element – This shows how redundant CSS can be when manually adding display: flex for each element.
Example 1: Using Tailwind CSS Utility Classes
Here, we apply the flex class from Tailwind to 100 elements. The flex class is defined once in the Tailwind CSS stylesheet.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tailwind Flex Example</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div class="grid grid-cols-5 gap-4 p-4">
<!-- 100 flex elements -->
<div class="flex items-center justify-center h-20 bg-blue-500">1</div>
<div class="flex items-center justify-center h-20 bg-blue-500">2</div>
<div class="flex items-center justify-center h-20 bg-blue-500">3</div>
<!-- Add 97 more elements with the same flex class -->
<div class="flex items-center justify-center h-20 bg-blue-500">100</div>
</div>
</body>
</html>
Example 2: Manually Writing display: flex
for Each Element
In this example, you are writing custom CSS for each of the 100 elements, which is inefficient and redundant. Each rule is repeated in the stylesheet.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Manual Flex Example</title>
<style>
/* Manually defining display: flex for each element */
.item-1, .item-2, .item-3, .item-4, /* add up to .item-100 */
.item-100 {
display: flex;
align-items: center;
justify-content: center;
height: 5rem;
background-color: #3b82f6; /* Blue color */
}
</style>
</head>
<body>
<div class="grid grid-cols-5 gap-4 p-4">
<!-- 100 manually flexed elements -->
<div class="item-1">1</div>
<div class="item-2">2</div>
<div class="item-3">3</div>
<!-- Add 97 more elements with manual flex styling -->
<div class="item-100">100</div>
</div>
</body>
</html>
Tejas’ Comment
I love this because it makes the case for Tailwind CSS beyond just the conveniences it offers, such as context switching, class naming, and base reset styles. Outside of these developer conveniences, there's also a clear benefit to the end user.
In practice, Tailwind CSS ships less code than traditional CSS. Typically, in an application, if you have 100 different classes that include the rule display: flex, you end up shipping that line—display: flex—100 times more than necessary.
However, with Tailwind CSS and its utility classes, the rule display: flex
is shipped only once. That's like a 100x improvement in how much code you ship, making it both efficient and performant.
There’s also something that Tailwind used to do where it would be as part of the PostCSS process where it reads your source code and looks at the class names you’re using and then from that big final generated CSS file would just remove classes you’re not using as a build optimizing. Is that still the case, how does that impact performance, positively of course?
According to the guest, Shurti Balasa, she explained that back in version 2, Tailwind CSS had tens of thousands of fixed class names. Every single class name you see in the documentation was included in the stylesheet. During development, you'd take that entire set of classes and only use the ones you needed, but you had to manually purge the unused ones before deploying to production. As a result, your production stylesheet would be different from your development stylesheet. That was how it worked back then.
Then came the Just-In-Time (JIT) compiler. What JIT does is it doesn’t generate the entire stylesheet during development. Instead, it only generates the styles that are actually present in your source code from the start. This means that your development stylesheet is the same as your production stylesheet. The JIT compiler became the default from version 3 onwards, so developers using Tailwind version 3 or later don’t even need to worry about the old purge process. If they come across discussions in blog posts or videos about purging stylesheets, they might wonder, "Do I have to do this?" But no, Tailwind now only generates the styles that are in your source code from the beginning, making the process more efficient.
Tejas' Comment
There's a big engineering optimization and performance insight here that many developers can take away.
From Shruti’s explanation, I’ve understood that in version 2, Tailwind first generates a large CSS file. Then, it read the source code to identify which classes were used and removed the unused ones from the CSS. However, with the Just-In-Time (JIT) compiler introduced in version 3, instead of generating a large bundle and trimming it down later (tree shaking), Tailwind starts by reading your code and only generates the necessary styles from the beginning.
For example, in code, JavaScript is super-optimized. The engines are optimized to the point where it’s very difficult, oftentimes, to de-optimize yourself in JavaScript—meaning to do something that causes the engine to do more work than necessary, resulting in slower performance. But people still do it. In React, for example, they try to make it easy to build performance-optimized applications, but sometimes, in an effort to optimize your code, you end up de-optimizing it. This can mean causing unnecessary memory allocation. In the case of imports, sometimes you can import modules in a way that doesn’t allow tree shaking, so you end up de-optimizing. In Tailwind, with the JIT and all these optimizations, are there ways developers should know that they may be de-optimizing their code? And if so, what can they do about it?
Shruti Balasa explained that, maybe not de-optimizing, but one thing developers need to know is that their class names will not be discovered if they’re broken. For example, let’s say you add a bg
somewhere, maybe in a React component, and you’re writing a conditional class name like:
// Incorrect Example
const Button = ({ success }) => {
return (
<button className={`bg-${success ? 'green-500' : 'red-500'} text-white p-2 rounded`}>
Click Me
</button>
);
};
In this case, the classes are broken in the code. Tailwind will not be able to discover them. The class names have to be complete like bg-green-500 or bg-red-500.
This is because Tailwind uses pattern matching, and for Tailwind, green-500 is not a valid class name, but bg-green-500 is. So, if you’re going to have conditional class names in your JavaScript files, they have to be complete. Don’t split them anywhere. That’s probably the only thing developers need to know.
// Correct Example
const Button = ({ success }) => {
const bgColor = success ? 'bg-green-500' : 'bg-red-500';
return (
<button className={`${bgColor} text-white p-2 rounded`}>
Click Me
</button>
);
};
Conclusion
In this episode of "Optimizing Performance In TailwindCSS", Shruti Balasa clearly explained the power and ease of use of Tailwind CSS, particularly focusing on its utility-first approach and performance optimizations. From breaking down common misconceptions about Tailwind’s file size to explaining the significant improvements brought by the Just-In-Time (JIT) compiler, Shruti Balasa makes a compelling case for why Tailwind is not just a developer-friendly framework but also highly efficient for large project development.
Her explanations and experience helped simplified how developers can avoid mistakes, and optimize their Tailwind usage for both simplicity and performance. Whether you're a beginner or experienced developer, this article provides you with key insights that can help you write better TailwindCSS in your projects.
Partners and Sponsors
The podcast this article is derived from is offered to you at zero cost and the way we do that is through partners and sponsors. Let’s take a moment to acknowledge them.
CodeCrafters
The podcast is partnered with code crafters. Code Crafters teaches developers how to get better at the tools they use every day and the languages they use. With Code Crafters you re-implement things like Redis, git docker, and tools you use in a language you know or want to get better at languages, like JavaScript or Python. By doing this, you get better at JavaScript or Python or whatever you choose and you also understand the tool implementing it from scratch in these languages. They have a generous free tier, but you could get 40% off if you use this link https://tej.as/codecrafters.
Stately.ai
has also partnered with stately.ai. Stately is a great way to visualize the flow of state across your application using state machine state charts. You can then export these state charts to actual code where you can fill in the business logic and create an application. If you would like to give that a try, visit stately.ai.
THAT Conference
Finally, the podcast is partnered with “THAT Conference.” THAT Conference is a family-style conference that happens in a water park twice a year in Wisconsin and Texas, and we often give away free tickets. If you want to attend that conference, share this episode on social media, and you will enter a raffle to do so.
PS: This article is an excerpt from the podcast. Listen to the full episode on Spotify and Apple. Don’t forget to follow, like, and share with your network!
Top comments (0)