DEV Community

Nikolaus Rademacher
Nikolaus Rademacher

Posted on • Edited on

Why Tailwind's utility-first approach is more than inline styles

We are using Tailwind CSS for our company's internal design system for over a year now and it is doing its job perfectly. Although most devs new to Tailwind are sceptical in the beginning, they embrace the setup after having written their first components with it, especially as it makes so much sense in a constrained environment like a design system.

In this post I want to explain my take on Tailwind's utility-first approach and what is doesn't have to do with inline styles…

Historically styles were abstracted away into CSS classes...

Historically, CSS styles have been abstracted away into a CSS class in order to be easily used in the HTML markup:

.my-component {
  width: 100%;
  padding: 1.25rem;
  border-radius: 9999px;
  background-color: black;
}

.my-component--text {
  color: white;
  font-weight: bold;
}
Enter fullscreen mode Exit fullscreen mode

So whenever you wanted to use these styles in your components, you would simply add their classnames to their corresponding HTML elements like this:

<div class="my-component">
  <strong class="my-component--text">Hello world</strong>
</div>
Enter fullscreen mode Exit fullscreen mode

This especially makes sense as defining styles with inline style attributes would lead to unnecessary duplication, bad readability and poor maintainability of your components.

... but nowadays the abstraction is the component

With modern JavaScript frameworks and libraries like React, Vue, Angular or Svelte, there is another abstraction: the component.

The above example in React could be abstracted away like this:

const MyComponent = ({ children }) => (
  <div class="my-component">
    <strong class="my-component--text">{children}</strong>
  </div>
);
Enter fullscreen mode Exit fullscreen mode

So now, when using the component you don't need to think about its styles at all. Just write:

<MyComponent>Hello world</MyComponent>
Enter fullscreen mode Exit fullscreen mode

This approach led to a mind shift in using CSS. Suddenly, even inline-styles made sense again: There is no need to abstract away style definitions, as they will only be written once – within the component itself.

The Tailwind approach

Tailwind provides many CSS classes that almost always do only one thing. These classes are referred to as utility classes. As their purpose is so limited they are perfectly suited to be reused in many places:

Utility class CSS Properties
.w-full width: 100%;
.w-auto width: auto;
.mt-5 margin-top: 1.25rem;
.mb-5 margin-bottom: 1.25rem;
... ...

But this is just a fancy way of writing inline styles, isn't it?

People keep comparing Tailwind utility-classes to inline styles and I certainly get their point. But there is a huge difference: Tailwind classes are constrained by what you define in Tailwind's configuration file:

In the configuration you will define your colors, spacings, fonts, sizes and so on that will be transformed into utility classes. This means that when sticking to Tailwind classes, you can't just write any style you like but you are constrained to what has been defined before. This is a huge difference.

In a design system you may think of the Tailwind's configuration as the "tokens" that are then used to create components.

The component above with Tailwind would look something like this:

const MyComponentWithTailwindStyles = ({ text }) => (
  <div className="w-full p-5 rounded-full bg-black">
    <strong className="text-white font-bold">
      {text}
    </strong>
  </div>
);
Enter fullscreen mode Exit fullscreen mode

Also check the Tailwind docs for a more comprehensive explanation: https://tailwindcss.com/docs/utility-first

What's your take on Tailwind?

Top comments (1)

Collapse
 
moopet profile image
Ben Sinclair

In your example, you're essentially hard-coding the text to be white on black, and using non-semantic elements to do so.

If you used semantic elements, you could style them consistently across the site, or use the cascade to style them differently when in, say, a sidebar vs. the header.

<header>
  <h1>title</h1>
  <p>{{plainText}}</p>
  {{formattedText}}
Enter fullscreen mode Exit fullscreen mode

Regarding the "mind shift",

There is no need to abstract away style definitions, as they will only be written once – within the component itself.

The problem I see with this is that, sure, your component is now completely isolated. You can pick it up and drop it into a different project and it'll look exactly the same, since react components generally namespace themselves in CSS. However, you'll have to edit the component to change "black" to "red" to match your theme, or use a system of abstracted colours, like "brand-primary-light-mode", etc., which will need to be provided by every project you use.

I'm not convinced this is better than the conventional way of saying, well, "it's a heading and it's in the sidebar, so it's going to be big and red".