Using Tailwind for four months
Tags: tailwindcss, css, frontend
Published: 2024-03-01 14:26:32

![Ship floating in the sky](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wmb4zsjg1xd8yup0ffs5.png)

[Versão em Português](https://dev.to/maiconsanson/4-meses-usando-tailwind-4k2l)

*This post was written in October 2023; some data provided here may have been changed.*

Personal projects are great; you can choose the entire [application stack](https://en.wikipedia.org/wiki/Solution_stack): database, environments, deployment, testing, back end, front end, how to organize tasks, and more. In these projects, you can achieve a level of learning in a company that you would hardly have. So, take advantage, 'play' startup in your free time, and bear the consequences of your decisions.

My focus is on the front end, but I dabble in the back end a bit because it's a great feeling to be able to, on my own, bring a crazy idea to life and put it into practice.

I'm a bit of an old-timer (+40) and was already working in development before the advent of [JQuery](https://coderwall.com/p/cekryw/history-of-jquery) and [Prototype](http://prototypejs.org/2007/01/18/prototype-documentation-is-here/). I never went to college for this field, but I graduated from a public university in Music and Fine Arts... go figure.

Anyway, I want to say that I've seen a lot out there. Concepts and ideas that come and go, old problems with new names, and old solutions with a new look.

But I have to focus, and I am here to talk about styles in a web application.

In recent years, working as a senior developer in companies of various sizes, I have increasingly distanced myself from CSS, which, modesty aside, I know very well and have always found fun and therapeutic. I've never complained about it, but it's common to use a pre-designed Design System, and you just 'program' the blocks (sometimes applying Atomic Design, which would already lead to another article when it makes sense) without worrying too much about the style of the components.

It is common for a project out there to use a [CSS-in-JS](https://en.wikipedia.org/wiki/CSS-in-JS) solution like [Styled Components](https://styled-components.com/), [Emotion](https://emotion.sh/docs/introduction), [Stitches](https://stitches.dev/), [Linaria](https://linaria.dev/), and others when adjustments are needed. They are great; I've used them for a long time and love using them when it makes sense.

Once, I used [Sass](https://sass-lang.com/) with [CSS Modules](https://github.com/css-modules/css-modules) to style a practical front-end test for a position at a medium-sized company. During the interview, one of the 'senior developers' asked me:

> Why use _Sass_? It's somewhat uncommon these days. Why didn't you choose _CSS-in-JS_? Don't you think it's a bad practice to go back to using _CSS_ or _Sass_?

I just smiled. However, over time, I've seen more and more front-end developers who knew very little about _CSS_, _Modules_, _Custom Properties_, the power of the cascade effect, and its countless possibilities. Professionals who enter the market only use _JS/TS_ as the default for writing styles and see _CSS_ as outdated. What a crazy situation!

Returning to my case, in this fresh new project, I decided to use [Server Components](https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023#react-server-components) along with [NextJs](https://nextjs.org/) and give a chance to the much-discussed [TailWind Css](https://tailwindcss.com/). It's a relatively complex project with a strong focus on the user experience, featuring a simple interface but with details of unique interactions and multiple themes.

With that said, here are my conclusions after four months of daily use.

## What is Tailwind CSS?

For those who landed here by chance, in summary, _Tailwind CSS_ is a _CSS_ framework similar to the old [Bootstrap](https://getbootstrap.com/).

The main difference is that **each class in _Tailwind_ corresponds to a CSS property** (or pseudo-class/element) but with a slightly different name. The so-called [Utility-first](https://heydonworks.com/article/what-is-utility-first-css/).

For example:

- The class `z-0` translates to the property `z-index: 0;`.
- The class `overflow-hidden` translates to the property `overflow: hidden;`.
- The class `hover:underline` translates to the pseudo-class `:hover { text-decoration: underline; }`.

## Controversial Aesthetics

In the examples we come across, including in the [documentation](https://tailwindcss.com/docs/installation) itself, we find this type of markup structure:

`<div class="w-9 h-9 rounded-lg flex items-center justify-center text-slate-700 peer-checked:font-semibold peer-checked:bg-slate-900 peer-checked:text-white">`

I care a lot about the functional aesthetics of code (beauty + readability), clean code with good sense, and organization.

When reading the above code, it bothers me. I find it difficult to understand what is happening, and I lose focus with all these classes cluttering my markup.

Markup and style should be isolated as much as possible, in my opinion. I don't need to know that an element has a `flex` position if I don't want to know that at the time.

My thought was:
> Alright, these are just examples, and I can work around it. 

I'll create a function to handle this.

And I resolved it this way:

For each component that contained styled elements, I created a `styles.js`.

```js
import { cleanClasses } from '@helpers'

export default function classNames({ hasSomeProp }) {
  return {
    content: cleanClasses(`
      ${hasSomeProp ? 'border-green' : 'border-black'}
      border
      flex
      h-9
      items-center
      justify-center
      peer-checked:bg-slate-900
      peer-checked:font-semibold
      peer-checked:text-white
      rounded-lg
      text-slate-700
      w-9
    `),
  }
}
```

_Note: I always sort my properties in alphabetical order._

And inside my component, I imported that file and applied it to the element.

```js
import classNames from './styles'

export default function MyComponent({ children, hasSomeProp }) {
  const c = classNames({ hasSomeProp } )
  return <div className={c.content}>{children}</div>
}
```

Solved, I thought!
I isolate all the classes and keep my components clean! 🤘

But notice that in `styles.js`, there's a function called `cleanClasses()`. I had to create it because the indentation using [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) was messing up the final [minification](https://en.wikipedia.org/wiki/Minification_(programming)) of the HTML.

To solve **and keep my indentation the way I think makes sense**, I created a function to remove unnecessary characters that could interfere with minification in the final build.

```js
import { isString } from '@helpers'

export default function cleanClasses(value) {
  if (!isString(value)) return ''

  const removeFalsyValues = value.replace(
    /\b(?:undefined|null|false|NaN|'')\b/g,
    '',
  )
  const removeExtraSpaces = removeFalsyValues.replace(/\s+/g, ' ')
  const cleanedClasses = removeExtraSpaces.trim()

  return cleanedClasses
}
```

I liked the solution, and it served me well, but I started to have a nagging feeling...

I enjoy programming, but I'm lazy; the less I have to code, the more I like coding. 😂

## Unnecessary Abstractions

The _Tailwind_ documentation became a part of my daily work routine. 
To ensure that a particular class represented the property I wanted, There was always a tab open for me to refer to.

And it's normal; I have to learn the framework. But this reliance on memorizing classes that are essentially property names (that I already knew by heart in _CSS_) started to irritate me, and I found the abstractions unnecessary.

In a way, I dare say, I was 'unlearning' the properties of plain _CSS_.

I used this term on purpose because after a few months using TW, and having already migrated the project to CSS Modules, I found myself adding Tailwind classes instead of CSS properties. As they are similar names, my brain ended up substituting these values.

For example, do you know which property the Tailwind class `justify-center` corresponds to?

- :radio_button: `justify-content: center;`
- :radio_button: `justify-items: center;`
- :radio_button: `justify-self: center;`
- :radio_button: `justify-tracks: center;`

Hint: `justify-tracks` is still experimental and doesn't even exist in Tailwind.

You'll have to go to the [documentation](https://tailwindcss.com/docs/justify-content) If you're in doubt. 🤡

## Handling more complex selectors

One aspect I didn't find productive was using more complex selectors. While styling a single element is okay, things get awkward when you need something more complex that involves other elements.

For example, let's say that I don't have access to an items list, but I need to modify them.
_Note: This is far from complex._

```jsx
<ul className={styles.ul}>
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>
```

With Tailwind, I would have to add something like:

```jsx
import { cleanClasses } from '@helpers'

export default function classNames() {
  return {
    ul: cleanClasses(`
      [&_li]:rounded-full
      [&_li]:border
      [&_li]:border-black
      [&_li]:bg-white
      [&_li]:px-2
      [&_li]:py-0.5
    `),
  }
}
```

It is very wordy to say that each property is an item in the list with [&_li] and have to inform that repeatedly in each TW class. Not to mention that I can't use [shorthand properties](https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties). There's a plugin, but I don't want to clutter my application with packages.

With CSS I just declare it like:

```jsx
.ul li {
  background: var(--color__white);
  border-radius: var(--radius__full);
  border: 1px solid var(--color__black);
  padding: var(--size__0-5) var(--size__2);
}
```

And I won't even go into detail about sibling selectors](https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-sibling-state) and other selectors that are trivial with native CSS. I feel like I'm reinventing the wheel, but this wheel isn't very round.

## Themes

Another thing I didn't like was the way of adding multiple themes and their various [design tokens](https://thedesignsystem.guide/design-tokens) using _TW_. I thought it would handle that natively, but it doesn't.

One option is to install an additional dependency like [tw-colors](https://github.com/L-Blondy/tw-colors), as we don't have that natively in the framework (we can only change the tokens of the current theme). It's nice and works well.

Another option is to use native CSS variables and inform Tailwind how to use them. But wait, I don't want that since I chose the framework to handle the styles. I only want to use Tailwind and its gang.

## Where is the cascade effect?

Quickly, I learned that TW removes one of the most important aspects of CSS (which, incidentally, is the meaning of the first letter of its acronym: **Cascade** Style Sheets).

_Note: The version used was `3.3.3`. They'll probably address this in the future._

For instance, there are situations where you need to override some style to customize existing components. Suppose you have a component used in various places, but in a specific location, you need it to have something different, and you only need to override a CSS property. Adding a new prop to the component to only use for this wouldn't be a good idea.

In the code below, due to the cascade effect, it's expected that the value `white` should replaced by `black` in the `background` property of `ChildComponent`. When we call this component, we add another background that should override the default.

In other words, everything declared later with the same [specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity) has a higher priority than the previous property.

The rendering of the div below would be `<div class="bgWhite bgBlack" />`.

```jsx
import { ChildComponent } from './components'
import styles from './styles.module.css'
// .bgBlack { background: black; }

export default function ParentComponent() {
  return (
    <ChildComponent className={styles.bgBlack} />
  )
}
```

```jsx
import styles from './styles.module.css'
// .bgWhite { background: white; }

export default function ChildComponent({ className }) {
  const bgStyles = [styles.white, className].join(' ')

  return (
    <div className={bgStyles}>
      Some Text
    </div>
  )
}
```

In Tailwind, if the rendering of your component is `<div class="bg-white bg-black" />`, the `bg-black` won't necessarily override the `bg-white`, as it depends on the internal order of classes in _TW_.

In other words, there is no guarantee of the cascade effect. Well, but that's what I pay CSS for! 😅

You can solve this, but guess what; you'll need to add another dependency to your project using [tailwind-merge](https://github.com/dcastil/tailwind-merge), which will remove the unwanted classes since the cascade effect doesn't work. Or use **!important**. 😒

## Decision after Analysis

Finally, as previously mentioned, I have concluded that, for my case, the ideal would be to remove everything related to _Tailwind_ (`tailwindcss`, `postcss`, `tw-colors`, and `tailwind-merge`) and use only CSS Modules (already provided by _NextJs_ as the default).

I found the experience of using Tailwind juicy, but it didn't convince me 100%. I had to create many extra solutions and install packages to make my experience the way I think it works.

Using it for prototyping projects, MVPs, or even if the team consists only of backend developers (who don't want to learn CSS) it can work. 

But if I were to create a new product, I would already rule out the use of Tailwind. To me, having well-organized design tokens with CSS variables and using CSS Modules is the most natural and efficient choice. I wouldn't even go for Sass. Doing the basics well is the trend for the coming years; it never stopped.

![Print de um pull request do Github](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gnfafcguzge2pb759slo.png)