DEV Community

maiconsanson
maiconsanson

Posted on • Updated on

Using Tailwind for four months

Ship floating in the sky

Versão em Português

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: 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 and Prototype. 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 solution like Styled Components, Emotion, Stitches, Linaria, 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 with 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 along with NextJs and give a chance to the much-discussed TailWind Css. 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.

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.

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 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.

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
    `),
  }
}
Enter fullscreen mode Exit fullscreen mode

Note: I always sort my properties in alphabetical order.

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

import classNames from './styles'

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

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 was messing up the final minification 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.

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
}
Enter fullscreen mode Exit fullscreen mode

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?

  • 🔘 justify-content: center;
  • 🔘 justify-items: center;
  • 🔘 justify-self: center;
  • 🔘 justify-tracks: center;

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

You'll have to go to the documentation 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.

<ul className={styles.ul}>
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>
Enter fullscreen mode Exit fullscreen mode

With Tailwind, I would have to add something like:

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
    `),
  }
}
Enter fullscreen mode Exit fullscreen mode

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. There's a plugin, but I don't want to clutter my application with packages.

With CSS I just declare it like:

.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);
}
Enter fullscreen mode Exit fullscreen mode

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 using TW. I thought it would handle that natively, but it doesn't.

One option is to install an additional dependency like 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 has a higher priority than the previous property.

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

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

export default function ParentComponent() {
  return (
    <ChildComponent className={styles.bgBlack} />
  )
}
Enter fullscreen mode Exit fullscreen mode
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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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, 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

Top comments (0)