Why Tailwind CSS is better than traditional CSS for large scale web apps?

10 Feb, 2022CSS

Tailwind CSS or other similar utility-first CSS framework is a great choice for large scale web applications. By "large scale web application" I mean it requires a team of multiple, not just one solo developer. In which case, writing CSS is not about creativity or aesthetics like creating a eye-catching landing page or a "special effects" to demonstrate how powerful CSS is, but rather a software engineering problem that involves developing and maintaining hundreds if not thousands of UI components, and the readability, maintainability, reusability, and scalability of your CSS (or styling) code matters more.

Tailwind CSS levels the gaps of CSS knowledge and coding styles among all the developers in the team by offering a subset/dialect of the CSS selectors, properties, and values. So majority of the web app layouts can be achieved with Tailwind CSS consistently regardless of the experience level of the developers.

What's even better is that Tailwind CSS is not intrusive, you may opt it in to your existing project to co-exist with your own design systems or 3rd-party UI frameworks like Material UI or Ant Design, and start to use it right away. And to avoid the risk of potentially breaking the existing styles, you can add a prefix and disable the preflight styles by adding this to your tailwind.config.js file:

module.exports = {
  prefix: 'tw-',
  corePlugins: {
    preflight: false,

Here're few examples how it helps overcome some of the possible problems for a web development team when writing vanilla CSS:

To avoid bad layout practices.

CSS is too flexible, there are multiple ways to achieve the same static layout, and each property has different types of unit, orders and shorthands. Inexperienced developers can easily make mistakes of using the wrong technique that visually looks identical to the requirement but not scaleable and requires constant refactoring whenever the requirement changes.

But Tailwind CSS does not only offers the utilities to achieve those common layouts that were curated by experienced developers, but also comes with a pretty comprehensive and easy to understand documentations to be used as a quick guide that provides "to achieve that I need to use this" kind of help.

What's even better, with the Tailwind CSS IntelliSense extension installed, you can hover on Tailwind CSS class names to preview the CSS properties and values that being used under the hood, which is also a way to learn new CSS syntax by seeing and using it in action.

Tailwind CSS IntelliSense Hover Preview

To avoid (or discourage) hardcoding values.

When writing vanilla CSS, it's tempting to copy the values like font-size, border-radius, colors etc. from the UI specs, or use absolute pixel values instead of relative rm or rem. While it is possible to make them as variables with the help of Sass or Less. By extracting those common values into variables and put them into a shared file, you can easily change them in the future when you need to support a different theme, dark mode for example, without having to search and replace all the occurrences.

However, Sass or Less always requires you to import the shared files that contain the variables, and it can quickly become a tedious task to do. On the contrary, once you configured the theme for Tailwind CSS, you can leverage the IntelliSense extension to autocomplete the variables without any extra importing to disrupt your workflow.

Tailwind CSS IntelliSense Hover Preview

To avoid over-complicated selectors and specificity issues.

Let's be honest,

CSS specificity sucks

-- @mdluo, 2022

Usually when people came across specificity issues, they would simply add one more meaningless class to the selector list, and if that still didn't work, they would start to throw !important and eventually your codebase is full of !importants.

And what's more, if you use Sass without CSS modules, you will probably write a lot of nested selectors, which generates a even longer selectors that is higher in the specificity value.

.dark-mode .components-card-news-snippet .news-stats[data-v-4911447f] .news-view-count .components-icon, .dark-mode .components-card-news-snippet .news-stats[data-v-4911447f] .news-publish-time .components-icon, .dark-mode .components-card-news .news-stats[data-v-4911447f] .news-view-count .components-icon, .dark-mode .components-card-news .news-stats[data-v-4911447f] .news-publish-time .components-icon {
  color: #fff;

PostCSS or CSS modules may also partially alleviate this problem by adding random keys to the generated selectors to flatten the nested selectors. However, you will still have to name your selectors which is a issue we will discuss in the next section.

Tailwind CSS tackles this problem elegantly by both pre-defining and generating on the fly (with JIT mode) individual utility class selectors that only contributes to one or very few class specificity values.

No need to name the classes!

There are only two hard things in Computer Science: cache invalidation and naming things.

-- Phil Karlton

Don't get me wrong, naming the classes is fine as long as everyone in the team follows the same rules or conventions such as old school BEM. But the "following" part is the hardest to achieve, it requires immense amount of discipline, particularly difficult among multiple developers and throughout a extensive period of time for a larger scale project.

Not to mention those purely decorative elements, such as wrappers, containers, dividers, etc. still require developers' effort to name them.

One less nested structure.

Deeply nested elements leads to deeply nested class selectors in Sass/Scss. It's also a common practice to start with creating a giant component to piece everything together to achieve the desired layout, and then break it down into many smaller components to make the code more maintainable.

One of the biggest problems with nested CSS selectors is that you can't shuffle the elements around to a different nesting level or remove any unnecessary intermediary elements without updating the selectors. It causes extra cognitive load for the developers to keep track of both DOM/JSX structure and CSS selectors structure.

React DevTools

The typical way of using Tailwind CSS is to apply the utility classes to the class attribute of the element or className property of the React component. By doing so, we naturally get rid of the nested selector structures and truly achieve what was claimed on the Tailwind CSS landing page -- "rapidly build modern websites without ever leaving your HTML".

React DevTools

Even though the class names could potentially get extremely long, with the help of prettier-plugin-tailwindcss or eslint-plugin-tailwindcss, they can be automatically sorted so that more impactful and structural class names are close to the element names, and decorative and stateful ones are further away to reduce distractions.

Another side effect is that by getting rid of the named classes, developers would spontaneously substitute them with using semantic elements and ARIA roles and attributes which is even more desirable.

People may argue naming the classes so it's easier to search and navigate the target elements in the Chrome DevTools' Elements tab. But with the React DevTools you can achieve the same thing in the Components tab without any extra effort.

React DevTools

Expose implementation details only when necessary.

Lacking of scope is another ugly part of the vanilla CSS. It hints that developers can override the styles of any elements with the correct class name even that the class name is for internal styling only. It resembles accessing private members in a class when doing object-oriented programming.

It's not a big deal if the whole project only consists of one or two components, but for a large scale project consists of hundreds or thousands of components, it's dangerous to expose such implementation (styling) details to seemingly public scope. Implicit dependencies (which is a bad practice) are going to be common thanks to the convenience of overriding internal stylings.

But when styling your components with Tailwind CSS, everything is private to the current component by default. So developers only expose the elements in the component that are supposed to be customisable. For example, when creating a dialog component, you can accept a className prop that applies to the container element, and a overlayClassName prop that applies to the overlay element, and confirmClassName, cancelClassName and closeClassName props that apply to the confirm, cancel and close buttons respectively.

Make it easy to copy and paste elements.

DRY principle has its limitations, we can't possibly achieve DRY by generalising everything. Sometimes it's healthier and more maintainable to copy existing styles and tweak them separately.

Powered by Gatsby. Theme inspired by end2end.

© 2014-2022. Made withby mdluo.