DEV Community

Cover image for Is Tailwind actually slow?
Aleksandrovich Dmitrii
Aleksandrovich Dmitrii

Posted on

Is Tailwind actually slow?

Tailwind managed to conquer the hearts of many developers and at the same time become the most despised CSS framework for many others. People love and hate it for a variety of reasons, but today I want to talk about TailwindCSS performance. Some skeptical developers believe that Tailwind can negatively affect loading and rendering times. But does it?

In this article, I want to address the following allegations:

  • “Tailwind may negatively affect the rendering time.”: Too many atomic CSS classes may cause additional CPU load.
  • "Tailwind may negatively affect the loading time.": It is not efficient lazy-loading-wise.
  • "Tailwind may negatively affect website cache.":
    • The size of generated HTML files is usually larger with Tailwind;
    • Generated CSS file may reset its cache much more often with Tailwind compared to BEM or CSS Modules.

A quick remark. Some problems that many developers attribute to Tailwind are actually problems of using the atomic styling approach. Tailwind does profess it, but you are not bound to use it only this way. In Tailwind you can also create components, which are good examples of non-atomic styling.

However, many developers use this tool just for atomic styling. And even in its documentation, Tailwind first encourages developers to use ‘multi-cursor editing’ to update repeated styles fast and then talks about using components. Hereby, I will talk about Tailwind problems from the perspective of using the atomic approach only.

Tailwind may negatively affect the rendering time

Why do some developers believe so? The common understanding is that the more CSS declarations you have, the slower the browser may process them. In the atomic styling approach, most declarations contain only 1-3 CSS rules, and in order to achieve required styling developers are forced to use a relatively large number of classes.

For example, to “center a div” using flex box, we can use 3 class names from Tailwind: flex, items-center, and justify-center. Each will generate a separate style declaration.

That’s not the case in the non-atomic approach. Using CSS Modules or BEM, we could create a single CSS declaration containing all three rules.

To test this utterance’s veracity, I decided to set up a benchmark.

  1. Create 2 sites: one is based on Tailwind, and another on CSS modules;
  2. Both display around 5K components, 20K DOM nodes in total.
  3. Each component has its own unique class name(s). Some styles are repeated among components, most of them not.
  4. Both sites have the same visual styling. All the CSS rules have the same specificity. The size of CSS files is the same for both sites (1.4 MB).
  5. In total, for Tailwind, 35K style declarations were generated, and for CSS modules, there were 10K style declarations.

Here’s an example of what a component looks like. JSX is used just for better readability. In reality, no framework was used in the benchmark.

// Tailwind Component, the value in braces [] is different for each of 5000 components
//  In addition, classnames 'underline', 'overline', 'line-through', 'no-underline', 'truncate', 'text-ellipsis', and 'text-clip', were used for some components
<article className="mt-[0.0px] mb-[0.0px] pt-[0.0px] pb-[0.0px] leading-[0.0px] z-[0] [&>header]:flex [&>header]:gap-[0.0px]">
  <header>
    <h2>Heading</h2>
  </header>
</article>

// CSS Modules Component, className is different for each of the 5000 components
<article className="egTxNhojHMGgAu4egvUQ">
  <header>
    <h2>Heading 0</h2>
  </header>
</article>
Enter fullscreen mode Exit fullscreen mode
// the number of px is different for each component
.egTxNhojHMGgAu4egvUQ{
  // repeats the same styling as Tailwind
}

.egTxNhojHMGgAu4egvUQ>header{
  // repeats the same styling as Tailwind
}
Enter fullscreen mode Exit fullscreen mode

The number of components is the same, so is the size of the CSS file. The only thing I am going to measure is how much the sole fact that the number of style declarations is 3.5 times greater may affect website's performance.

Alright, the difference is 3.5 times or 25,000 declarations. We surely will notice a drastic performance difference, won’t we? Let's measure how much time a browser may take to render a page. We will not take loading time into consideration and will only measure the parsing duration after the files are downloaded.

.

  • Chrome rendered the Tailwind site in 169 ms vs. CSS modules in 121 ms (28% slower).
  • Safari rendered the Tailwind site in 406 ms vs. 272 ms for Modules (33% slower).
  • Firefox rendered Tailwind in 114 ms vs. 82 ms for Modules (25% slower).

Well, that’s quite a difference. But before jumping to conclusions, let's do something extra. You may have noticed that the className in the Tailwind component is considerably longer than in the CSS modules one. Which leads to generating considerably larger HTML: 1.1 MB vs. 0.4 MB. Is there a chance that the size of HTML affects the results? Let’s try generating HTML of the same size by increasing the length of CSS modules class names.

// Tailwind Component
<article className="mt-[0.0px] mb-[0.0px] pt-[0.0px] pb-[0.0px] leading-[0.0px] z-[0] [&>header]:flex [&>header]:gap-[0.0px]" />

// CSS Modules Component
<article className="a-----------------------------------------------------------------------------------egTxNhojHMGgAu4egvUQ" />
Enter fullscreen mode Exit fullscreen mode

Now, the size of HTML will be the same: 1.1MB. However, in such a case, the size of the CSS file for the CSS Modules assembly becomes significantly larger compared to the Tailwind CSS file: 2.7 MB vs. 1.4 MB. Will such a change make Tailwind faster than CSS modules?

And the answer is simple: "No". Just the length of class names adds a couple of milliseconds, but doesn't change the picture drastically. Even if the size of the HTML file is the same, even if the size of the CSS Modules' CSS file is twice as large as Tailwind CSS file, the sheer difference in the number of CSS declarations will result in the rendering time to be longer.

We can conclude that using Tailwind and the atomic approach in general may in fact affect rendering performance negatively. However! This benchmark of mine is not 100% realistic, is it.

The problem is not with the atomic approach itself, rather with the huge number of style declarations. And if you use Tailwind and only use its class names for general problems, you will not have such problems.

  • For example, you can add a margin of mt-1, mt-2, mt-3, mt-6, or mt-8, using Tailwind, but if for some reason you need to add a margin like mt-[7px], mt-128, or other unique number of pixels, it's better not to use Tailwind.
  • The same goes for other arbitrary syntax, like [&>header]:flex. The probability you will reuse such class name is always super low. And therefore it's better to avoid using such classes.

📌 Using Tailwind and the atomic approach may in fact affect rendering performance negatively. But only if you use Tailwind incorrectly. As long as you are not defining unique classnames using arbitrary syntax, you are safe.

Tailwind may negatively affect the loading time

Tailwind is designed in a way of not relying on lazy loading. If you have a page that can be loaded lazily, and you declare its styles using Tailwind, all the styles will be included in the initially loaded CSS file. If you have 100 lazy loaded pages, all the styles of 100 pages will be included into the initially loaded css files. And that's not the case for CSS modules or BEM.

Initially loaded files are the most important in the user's perspective. And it is extremely important to handle your lazy loading properly. You can read my series of articles to understand why: How to make your app indefinitely lazy: The Ultimate Guide

To illustrate the problem, let’s do a small benchmark. I will extrapolate the previous example and create 2 extra similar lazy-loaded pages and make all of them have their own unique styles.

// Page 1 components
<article className="mt-[0.0px] mb-[0.0px] pt-[0.0px] pb-[0.0px] leading-[0.0px] z-[0] [&>header]:flex [&>header]:gap-[0.0px]" />

// Page 2 components
<article className="mt-[0.00rem] mb-[0.00rem] pt-[0.00rem] pb-[0.00rem] leading-[0.00rem] z-[0] [&>header]:flex [&>header]:gap-[0.00rem]" />

// Page 3 components
<article className="mt-[0.0em] mb-[0.0em] pt-[0.0em] pb-[0.0em] leading-[0.0em] z-[0] [&>header]:flex [&>header]:gap-[0.0em]" />
Enter fullscreen mode Exit fullscreen mode

With the example above:

  • Assembly with Tailwind generates 1 CSS file with the size of 3.9 MB
  • Assembly with CSS Modules generates 3 CSS files, each with the size of 1.4 MB.

Not only the size of the Tailwind CSS file will be downloaded 3 times slower, the browser will take more time to parse all the style declarations.

However, again, this problem only appears if you are missing Tailwind and are creating too many unique class names. As long as you stick to using the most general class names, even if you use up to 200 of them, the difference in loading time will be negligible.

📌 Misusing Tailwind and the atomic approach may in fact affect initial loading time negatively.

Tailwind may negatively affect website cache

There are 2 things I want to talk about here. First thing first, HTML.

Tailwind generates larger HTML

Files like JavaScript, CSS, SVG, fonts, they all might become decently cached, if you set up NginX for your static files. There are also a few caveats on how to make JavaScript files more cacheable beyond just using NginX, and you can read Part 3 of my Ultimate Guide to understand how to make them as cacheable as possible. But with HTML, it's different.

We can't just apply an aggressive cache strategy for the HTML file. HTML file is the main entry point to our website. If the file itself is taken from the cache, the entire application will be taken from it. Can you imagine you've fixed a bug, but the user cannot see the fix because they are still using the old version of the website?

Another problem is that if you are using SSR, sometimes using cache for HTML is not possible. For example, if your application is highly cautious about security, caching pages that contain personal data, might be risky. Also, misusing cache may affect your SEO optimizations.

Knowing that, developers who do work with SSR (Server-Side Rendering) or SSG (Static Site Generation) tend to be cautious about HTML cache. They often make cache time span as little as possible, and sometimes don't use any HTML cache at all.

It means that we cannot rely on HTML cache's when it comes to the loading time. And therefore, the only thing we can do to make HTML load faster, is to make the file shorter.

Now, what's the problem with Tailwind? Tailwind encourages developers to use the atomic approach, making each component contain many class names, quite often dozens of them. Just compare these 2 implementations of a button with the same styles:

// Tailwind
<button className="transition-all bg-blue-500 text-white py-1 px-2 rounded hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer active:relative active:top-[1px]" />

// CSS modules, by default the class name is 20 characters long
<button className={styles.button} />
Enter fullscreen mode Exit fullscreen mode

In the Tailwind implementation, the length of the class name might be 20 times longer. Now, imagine such a button is used 100 times on a page. And now imagine, every component on the page, has a class name that is 2-20 times longer. Tailwind may in fact generate a significantly large HTML file.

You may object by saying that we can use components in Tailwind. And that is true. But I don't know a single developer who does that, AND as I mentioned, Tailwind itself encourages developers to use the atomic approach. And if you do follow this recommendation, you should be aware:

📌 If your website uses SSR or SSG, using Tailwind CSS may result in longer HTML files, which may result in longer initial loading time.

Tailwind reduces cacheability of initial CSS files

I did mention that with Tailwind, all the Tailwind class names will be included into the initial CSS file. That means that if you start to use a new Tailwind class name, your initial CSS file will update its content. And every time a static file updates its content, we need to reset its cache in order for the browser to be able to download it.

It means that every update on your website may result in cache to be lost, which may make your initial loading time to increase.

Even if you don't use arbitrary class names, and only apply Tailwind for general styling, there's a good chance you will be adding new class names from time to time. And each time you do that, the cache of the initial CSS file will be lost.

And I'm sorry about promoting my lazy loading series again, but cache is a huge topic. It would be better if you read the 3rd part of my series to better understand on why the cache needs to be reset. And to better understand on why in fact for Tailwind it's not that big a deal.

Yes, when using Tailwind, your initial CSS file is predisposed to lose its cache often. But so are initial JavaScript files. Regardless of what change you make, initial JavaScript files will lose their cache too. And here's the thing. If a single initially loaded file loses its cache, the initial loading time increases significantly. But if 2 of them lose their cache, they will be downloaded in parallel, and the loading time will not suffer as drastically.

Knowing that, we can conclude that it's actually not that scary that Tailwind may result in your initial cache to be reset. Unless, you care about how much data users are spending to download your site. 99% of projects don't, but I do know a few cases, where developers try to make their applications work in conditions, where users are forced to use 2g Internet connection. If you are working on one of such projects, you must be aware of this problem.

📌 Tailwind generated CSS files are predisposed to lose their cache more often compared to CSS modules files. However, if you don't care about extreme cases when users use extremely slow Internet connection, you shouldn't care about this problem, because initial JavaScript files lose their case even more often.

Can using Tailwind be justified, performance wise?

I'd say yes. Objectively, Tailwind, or it's better to say the atomic approach, does have some problems with performance. Realistically, unless you really care about every tiny bit of performance optimizations, the problems it has are not that crucual.

I know projects, that have strong performance requirements, with millions users a day, tremendous beautiful projects. And such projects probably should not stick to using the atomic approach.

Although, using Tailwind might be beneficial too. Tailwind is good in providing general styling. The more it is general, the better Tailwind good at it. Thus, if you define most of your styles using CSS Modules, and limit use of Tailwind in utility classes only, you can even improve the performance of your application. For example, if a component just need margin or flex styles, Tailwind could be treated as a good fit. And in other cases you could use CSS Modules. And with such an approach, when you combine Tailwind and CSS modules, you can reduce the number of style declarations. Which will improve loading and rendering time.

But in the end, for most of the projects, it's more of the preference question. Even if you do misuse it, the impact for small or medium size projects will be small.

Alright, that's it. Thank you for reading. You are welcome to leave a comment if you have any question.

Here are my social links: LinkedIn Telegram GitHub. See you ✌️

Top comments (0)