Why TailwindCSS is the Wrong Choice for Your Application

Find out why this CSS framework will hinder long-term code maintainability and scalability beyond an early prototype.

By Tom Sturge

Seasoned Leader and engineer with over 20 years in the tech sector. Expert in driving team growth, mentoring, and delivering innovative tech solutions across diverse projects

May 6, 2024 | 8 mins

TailwindCSS has gained immense popularity in recent years, touted as a utility-first CSS framework that promises rapid development and consistent styling across projects. However, upon closer inspection, TailwindCSS presents several significant drawbacks that make it an unsuitable choice for many applications, particularly those with long-term maintenance requirements or complex styling needs.

Abstracting Readable CSS

One of TailwindCSS's primary criticisms is its reliance on a vast array of utility classes, resulting in what is often called "classname soup." Instead of using semantic, descriptive class names, developers must string together multiple utility classes to achieve the desired styling. This approach can quickly lead to bloated and convoluted markup, making it challenging to understand and maintain the codebase.

html
<!-- TailwindCSS -->
<div class="flex items-center justify-between p-4 bg-gray-200 rounded-lg shadow-md">
  <!-- Content -->
</div>

<!-- Vanilla CSS -->
<div class="container">
  <!-- Content -->
</div>
css
/* Vanilla CSS */
.container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem;
  background-color: #e2e8f0;
  border-radius: 0.5rem;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
}

This example shows how the TailwindCSS classnames usage make it difficult to know that the element is a container and often lack self-documenting insights to what styling is being applied. Where as the Vanilla CSS has a clear indication of the styling's role and the CSS is easy to understand.

While TailwindCSS advocates claim that this approach promotes consistency and reduces the need for custom CSS, the reality is that it often results in a trade-off between readability and maintainability. As projects grow in complexity, the classname soup becomes increasingly difficult to decipher, hindering collaboration and making it harder for new developers to understand the codebase.

Lack of Support for CSS Features

Another significant drawback of TailwindCSS is its lack of support for modern CSS features and specifications. While the framework provides a limited set of utility classes, it fails to embrace CSS's full power and flexibility, leaving developers without access to many well-supported and widely adopted features.

For example, TailwindCSS does not natively support CSS variables (custom properties), a powerful feature that allows for dynamic styling and better code organisation. Similarly, it lacks support for advanced selectors, such as the `:has()` pseudo-class, which can significantly simplify and enhance styling capabilities.

By limiting developers to a predefined set of utility classes, TailwindCSS effectively restricts their ability to leverage the full potential of CSS, ultimately leading to less efficient and less maintainable code. If a developer needs support for CSS features TailwindCSS hasn't catered for, another library is needed to add support or custom CSS is needed, defeating the very point of the framework.

Difficulty debugging

One of the most significant challenges developers face when working with TailwindCSS is difficulty debugging styling issues. Unlike traditional CSS, where styles are defined in separate stylesheets, TailwindCSS generates its styles dynamically based on the utility classes used in the markup.

This approach makes it incredibly challenging to trace the source of a particular style or to understand the cascade and specificity of styles applied to an element. Developers often find themselves sifting through generated CSS files or relying on browser developer tools to inspect and modify styles, which can be time-consuming and frustrating.

TailwindCSS's approach of providing many utility classes to fit all use cases can lead to complex and unintuitive class combinations, making it difficult for developers to understand the intended styling and the interactions between different utility classes, especially in larger codebases or when working with code written by others.

Unnecessary Extra Learning Curve

TailwindCSS twists basic CSS principles and concepts. It obscures even the most straightforward CSS styling into a string of abstract classnames, requiring developers to memorise many utility class names to use the framework effectively. This goes against the principles of good software development, which prioritise intuitive and self-explanatory code.

Additionally, experienced frontend developers accustomed to writing vanilla CSS may find it challenging to understand what a long list of classnames is doing at a glance, often requiring consultation with the documentation to decipher the styling intent. In contrast, well-written vanilla CSS can be more self-explanatory and more straightforward to comprehend for experienced developers.

Alternatives to TailwindCSS

While TailwindCSS may offer some benefits for rapid prototyping or small-scale projects, its drawbacks become increasingly apparent as applications grow in complexity and longevity. Fortunately, several alternatives provide a more robust and maintainable approach to styling:

  • CSS Modules: CSS Modules allow developers to write modular, component-scoped CSS while maintaining the benefits of traditional CSS syntax and tooling. This approach promotes better code organisation, easier maintenance, and improved collaboration.
  • CSS-in-JS: Libraries like styled-components and emotion enable developers to write CSS directly within their JavaScript or TypeScript files, leveraging the full power of JavaScript and enabling dynamic styling based on component state or props.
  • Sass/SCSS: Preprocessors like Sass/SCSS offer a more powerful and flexible way to write CSS, with features like variables, mixins, functions, and nesting, while still maintaining a familiar syntax and tooling ecosystem.
  • Vanilla CSS with BEM or CUBE CSS: By adhering to naming conventions like BEM (Block Element Modifier) or CUBE CSS, developers can write maintainable and scalable CSS without needing a utility-first framework like TailwindCSS.

While each of these alternatives has its strengths and weaknesses, they all prioritise readability, maintainability, and the ability to leverage the full power of CSS, making them better suited for long-term projects and complex styling requirements.

Reusable Design Tokens without the Bloat

One of the claimed benefits of TailwindCSS is the ability to reuse utility classes for consistent spacing, colours, and typography across a project. However, implementing a simple token design system can achieve this without the bloat, unreadability, and maintainability issues that come with TailwindCSS. A token design system involves defining reusable variables or constants for design properties like spacing, colours, and typography, which can then be used throughout the codebase. This approach provides the benefits of consistency and reusability without the need for verbose utility classes in the HTML markup.css:

css
/* Design Tokens */
:root {
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --color-gray-200: #e2e8f0;
  --font-family-sans: 'Roboto', sans-serif;
}

/* Usage */
.container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--spacing-md);
  background-color: var(--color-gray-200);
  border-radius: var(--spacing-sm);
  font-family: var(--font-family-sans);
}

By adopting a token design system, developers can achieve consistent styling across their applications without the drawbacks associated with TailwindCSS, such as HTML bloat, reduced readability, and maintainability challenges.

Conclusion

While TailwindCSS may offer some benefits for rapid prototyping or small-scale projects, its drawbacks become increasingly apparent as applications grow in complexity and longevity. The classname soup, lack of support for modern CSS features, and difficulty in debugging make it an unsuitable choice for many applications.

By embracing alternatives like CSS Modules, CSS-in-JS, Sass/SCSS, or vanilla CSS with naming conventions, developers can achieve better code organisation, maintainability, and the ability to leverage the full potential of CSS.