A couple of weeks ago, I removed all the Tailwind CSS from my website. It's not that I don't like Tailwind or I think it's bad — I've just been really enjoying writing pure CSS.

I've also been at war with some websites built using Tailwind lately, and it's made me shy away from utility classes.

Pitfalls

I've been working in a couple of codebases lately that have been challenging to read and difficult to make quality updates in. Every styling update seems to require pulling on a long thread to find all the areas impacted by the change. I'm not talking about impact from the cascade. Rather, many styles have utility classes repeated in several places in slightly different ways.

Utility Classes For Everything

I think the easiest trap to fall into is only relying on Tailwind's utility classes. Obviously, the whole idea of Tailwind is to enable rapid development through the use of utility classes. But when you're repeating the same group of classes across multiple elements, you can really benefit from creating your own reusable class or better yet, adding default styles to the HTML element.

This is a basic example, but imagine you have an unordered list like the one below:

html

<ul class="list-none space-y-3">
  <li class="text-sm lg:text-lg">
    <a href="#" class="block text-green-600 hover:underline">Lorem ipsum dolor sit amet, consectetur adipiscing elit</a>
  </li>
  <li class="text-sm lg:text-lg">
    <a href="#" class="block text-green-600 hover:underline">Aliquam eu ultrices magna</a>
  </li>
  <li class="text-sm lg:text-lg">
    <a href="#" class="block text-green-600 hover:underline">Maecenas egestas sapien vitae magna bibendum placerat</a>
  </li>
  <li class="text-sm lg:text-lg">
    <a href="#" class="block text-green-600 hover:underline">Aenean ultricies nisi id turpis ornare egestas</a>
  </li>
</ul>

Only using utility classes means you're repeating yourself for every list item and violating one of the most basic principles of programming. We can simplify it into something like this.

html

<ul>
  <li>
    <a href="#">Lorem ipsum dolor sit amet, consectetur adipiscing elit</a>
  </li>
  <li>
    <a href="#">Aliquam eu ultrices magna</a>
  </li>
  <li>
    <a href="#">Maecenas egestas sapien vitae magna bibendum placerat</a>
  </li>
  <li>
    <a href="#">Aenean ultricies nisi id turpis ornare egestas</a>
  </li>
</ul>

<style>
	ul {
		list-style: none;
	}
	
	li {
		font-size: 10px;
		
		@media screen and (min-width: 992px) {
			font-size: 18px;
		}
	}
	
	li:not(:last-child) {
		margin-bottom: 0.75rem;
	}
	
	a {
		display: block;
		color: green;
		
		&:hover {
			text-decoration: underline;
		}
	}
</style>

Obviously, we end up writing more code by separating the CSS from the HTML. But if we have this kind of list in multiple places throughout our application, we're repeating ourselves every time, and adding more work for ourselves later if we ever decide to update the unordered lists.

Overusing Arbitrary Values

One feature of Tailwind is the ability to use arbitrary values in your utility classes. I think this feature opens up a can of worms that they really didn't intend to open.

Arbitrary values can be helpful when you need a one-off value in a pinch. But if half the classes you're writing are arbitrary values, you should just make a custom class and write your CSS there.

html

<div class="h-[140px] md:h-auto lg:h-[140px] xl:h-auto order-first md:order-last lg:order-first xl:order-last col-span-1 thumbnail items-stretch relative rounded-1-[9px] hover:shadow-[-7px_0_6px_-2px_rgba(0,0,0,0.3)] basis-[20%] lg:basis-[45%]"></div>

We can simplify this, greatly improve the readability, and make it more maintainable.

css

.thumbnail {
  height: 140px;
  order: -9999; /* order-first */
  grid-column: span 1 / span 1;
  align-items: stretch;
  position: relative;
  border-radius: 9px;
  flex-basis: 20%;
  
  &:hover {
	box-shadow: -7px 0 6px -2px rgba(0, 0, 0, 0.3);
  }

  @media (min-width: 768px) {
		height: auto;
		order: 9999; /* order-last */
  }

  @media (min-width: 1024px) {
		height: 140px;
		order: -9999; /* order-first */
		flex-basis: 45%;
  }

  @media (min-width: 1280px) {
		height: auto;
		order: 9999; /* order-last */
  }
}

No Standardization

I wouldn't say this is specific to Tailwind, but in general, code maintainability really suffers when you're not following any kind of standard. When your Tailwind classes are written in no particular order, they can be really difficult to parse, especially weeks, months, or years later.

The easiest way to add standardization to your Tailwind project is to enable automatic class sorting with Tailwind's Prettier plugin.

Keep It Simple

I say this from experience, but I think it's really easy for developers to overcomplicate things when we just need to stick to the basics. In the spirit of efficiency, it's easy to spend time building convoluted solutions for simple problems (or non-problems).

In one of the projects I'm working on, I found something like this. Instead of writing one reusable CSS class, we added a list of utility classes to a PHP variable and repeated that variable in several spots.

php

$repeatable_classes = 'peer absolute -right-1 -top-1 rounded-full border-green-500 text-green-500 opacity-0 ring-green-500 checked:opacity-100 focus:ring-2 focus:ring-green-500 group-focus-within:opacity-100 group-hover:opacity-100 group-focus-within:ring-2 group-hover:ring-2';

This is a situation where simply writing a custom CSS class makes much more sense.

Final Thoughts

I still think Tailwind is a great tool for quickly building out website layouts. But it's important to have a plan for standardizing your code so it's easier to read and maintain. It's totally fine to prototype designs using only utility classes, but I believe it's a mistake not to refactor areas where you can cut down on repetition.