5 Tailwind CSS Anti-Patterns to Avoid

Tailwind CSS has been a game-changer for many developers over the past few years, offering a utility-first approach that helps quickly translate design into code. It’s not that Tailwind itself is an anti-pattern; on the contrary, it’s a very useful tool. It enables developers to move faster and style elements with more granularity. Moreover, it helps to reduce the gap between structure and style, making it easier to see the design directly in the markup.

However, like any powerful tool, it’s possible to misuse Tailwind CSS, leading to less maintainable code and other complications. Here are some anti-patterns you should be wary of when working with Tailwind CSS.

1. Using the built-in color palette

Tailwind comes with a comprehensive color palette which is great for prototyping. However, relying solely on the built-in palette for your final product can lead to a lack of brand consistency. Worse, it can get you into a state where your colors lack semantic meaning and thus aren’t used consistently throughout your application.

Tailwind’s default color palette is expansive, and probably overkill for your project.

Instead, you should create semantic names for all the colors you use throughout your project, and completely replace the default palette with them.

If you want to support multiple themes, consider defining your colors as CSS variables, rather than within your Tailwind config. This allows you to assign multiple colors to the same semantic name depending on the theme selected.

2. Under-utilizing theme customization

One of the best aspects of Tailwind is its design-by-constraints philosophy. Rather than styling everything with magic numbers, Tailwind’s utility-first approach forces you to choose styles from a predefined design system, making it easier to build consistent user interfaces. However, the built-in Tailwind theme doesn’t always match the design system used by your application. Because of this, it’s easy to fall into use of “magic classes,” where utility classes are used willy-nilly without adherence to an established design system.

Similar to overriding the default color palette, you can (and should) configure the entire theme to fit your application’s design system, including typography, breakpoints, spacing, and more. This will help you enforce consistent use of styles across your application.

3. Not using a component-based framework

Tailwind is best leveraged in a component-based architecture like React, Vue, or Svelte. While it’s possible to use Tailwind in regular HTML, the lack of reusability can cause a lot of duplication. The Tailwind docs advise you to use multi-cursor editing to circumvent this. However, it’s a risky maneuver requiring you, the fallible human, to ensure you always have every instance of the duplicated code selected.

In contrast, when using a component-based framework, you can define a component once and reuse it throughout your application. Here’s an example using React:


// Define a reusable Button component using Tailwind utility classes
const Button = ({ children, onClick }) => {
  return (
    <button
      onClick={onClick}
      className="bg-primary hover:bg-primary-alt text-primary-contrast py-2 px-4 rounded"
    >
      {children}
    </button>
  );
};

// Use (and re-use) the Button component without having to duplicate Tailwind classes
const App = () => {
  return (
    <div>
      <Button onClick={() => console.log('Clicked!')}>Click Me</Button>
      <Button onClick={() => console.log('Clicked too!')}>Click Me Too</Button>
    </div>
  );
};

4. Using the @apply directive

Tailwind lets you inline existing utility classes in your own custom CSS via the @apply directive. While it can be useful for specific use cases, like overriding styles in a third-party library, it breaks Tailwind’s utility-first paradigm. One of the strengths of utility classes is their composability; you can mix and match them directly in the HTML. When you use @apply, you create a fixed set of styles that are less flexible and harder to customize without going back to the stylesheet.

To use the @apply directive effectively and avoid these issues, it’s important to:

  • Limit its use to scenarios where it makes sense, such as when you need to override styles from third-party libraries.
  • Ensure that any custom class created with @apply is truly reusable and not just a one-off style that could be handled by utility classes and a self-contained component.

5. Using Tailwind at all in domain-level components

While Tailwind is great for implementing reusable component libraries, its utility should end there. If you’re building components that fulfill higher-level business requirements (e.g. a checkout form on an e-commerce site), you shouldn’t be reaching for Tailwind’s utility classes. Instead, you should have already built out reusable components for all the required pieces, including form inputs, buttons, and layout containers. Otherwise, you’ll end up with a lot of duplication across your front-end, making it harder to ensure that styles are being used consistently throughout.

Conclusion

While Tailwind CSS offers a powerful and flexible utility-first approach to styling web applications, it’s important to be mindful of common pitfalls that can compromise the maintainability and scalability of your code. By thoughtfully integrating Tailwind with a consistent design system, embracing component-based architecture, and creating a library of reusable components, you can build clean, consistent, and highly maintainable user interfaces.

Conversation

Join the conversation

Your email address will not be published. Required fields are marked *