An Honest Look at Tailwind as an API for CSS

Tailwind CSS is a utility-first CSS framework that receives a generous and equal amount of affection and hatred. When I first used Tailwind on a project, it was hard to know if I was making the right decision after searching for advice, and I had many questions.

Luckily, after employing Tailwind on several projects, I have found some best practices that can lead to readable CSS enjoyed by both designers and developers.

Why Tailwind?

First off, you may be asking yourself, why should we use Tailwind? I’ve seen various arguments why someone (especially someone new to CSS) should steer clear of Tailwind.

In theory, I understand the apprehension. Suppose you’re not familiar with the concepts behind CSS just yet; it might not make sense to reach for an abstraction of those concepts. But, I think there is merit in investigating what becomes more straightforward when incorporating Tailwind into a project.

Less CSS is more

You now only need to recall a single CSS class instead of a key-value pair; for instance: flex vs. display: flex. You’d be surprised how helpful this is during the debugging process. You can try out a bunch of different CSS properties just by swapping classes on the DOM element. And because Tailwind has defined the CSS classes for you, you only need to focus on the DOM elements themselves. The benefit here is that you can quickly sketch in CSS, in the inspector, or right in your HTML.

Let’s take a look at what a few display-oriented utility classes look like in Tailwind for context:


    .flex { display: flex; }

    .inline { display: inline; }

    .table  { display: table; }

    .hidden { display: none; }

As you see above, most of the time, the class’s name is taken directly from the CSS property value. However, sometimes there are deviations from the norm, so it’s good to have your favorite Tailwind Cheatsheet handy.

Bye-bye browser concerns

When selecting a particular CSS property, you should consider if it is the best tool for the job and if browsers currently support it. And while memorizing currently supported CSS properties can be time-consuming and laborious, Tailwind has simplified that for you by only using browser-supported features and automatically adding vendor prefixes where support is spotty. Additionally, there is no need to do a caniuse lookup, and, as a bonus, if you need to support Internet Explorer 11, you can always use Tailwind CSS v1.9.

Why not vanilla CSS?

It might be tempting to go it alone with vanilla CSS, especially with how far CSS has come straight out of the box. And that’s a viable solution for more straightforward projects. But, having worked on a project that shared styles between React Native (JavaScript) and Ruby on Rails (Embedded Ruby), it was instantly beneficial to have Tailwind configured the same in both repositories. This would get both applications on the same footing. And, since multiple developers with different specialties would be working with these styles, it was helpful to have a common language between the two repositories.

Our tailwind.config.js file defines our custom colors, fonts, and other global preferences. We can choose whether we’d like to extend the default theme or override it, if we’d like to start from scratch. Here is where we get to incorporate parts of our design system and where I believe Tailwind steals the show.


    // tailwind.config.js
    module.exports = {
      theme: {
        screens: {
          sm: '400px',
          md: '700px',
          lg: '900px',
          xl: '1400px',
        }
      },
      extend: {
        screens: {
          '2xl': '1600px',
        {
      }
    }

Above, we can see that we’ve overridden the screen width values (sm, md, lg, and xl), and we added the even wider 2xl to our system. We get to benefit from Tailwind’s organized naming conventions and build off of them.

We can also define how many ems our spacing values refer to (sm, md, lg, xl, etc.) or what numeric font-weights (100, 200, 300, etc.) refer to which helper keyword (font-normal, font-bold, etc.). And I haven’t even begun to mention the built-in support for animation and even theming (hello: dark mode!) in Tailwind 2.0.

How to use Tailwind effectively

Using Tailwind utility classes is akin to ordering from the restaurant’s menu, while editing the Tailwind configuration is like writing the menu with knowledge of what is in the pantry. You can mix and match with the latter, combine and omit to your heart’s content.

Since the most common criticism of Tailwind CSS (and most utility-first CSS frameworks) is that the DOM becomes cluttered and filled with CSS classes, let’s start there. Adding the presentation layer to the DOM like this can be distracting. It also gets in the way and makes minor edits much harder than needed.

Let’s take a look at a button element from Tailwind’s own marketing page


    <a class="w-full sm:w-auto flex-none bg-gray-900 hover:bg-gray-700
    text-white text-lg leading-6 font-semibold py-3 px-6 border
    border-transparent rounded-xl focus:ring-2 focus:ring-offset-2
    focus:ring-offset-white focus:ring-gray-900 focus:outline-none
    transition-colors duration-200">
      Get started
    </a>

As you can see, there are many Tailwind CSS classes here. We are defining hover states (hover:bg-gray-700), focus states (focus:ring-2, focus:ring-offset-2, focus:ring-gray-900, focus:outline-none), changing the width of the button based on the size of the browser (w-full sm:w-auto), updating text styles (text-white, text-lg, leading-6, font-semibold), and even adding CSS transitions (transition-colors duration-200).

I assume the bright minds over at Tailwind are choosing to publish the default form of Tailwind to their marketing site to advertise the promise of their technology as it works by default. That way, anyone willing to inspect the page can get an immediate sense of what’s possible.

However, let’s take their button to the next level and extract the complexity out of the DOM and back into a CSS (or, SCSS rather) file.

An API for CSS

The best way to use Tailwind is to treat it as an API for CSS. We can confidently use Tailwind’s utility classes and construct them into sensible groupings that we will leverage as repeatable components. Doing this would be similar to making an API request, with the particular properties suited to your exact need.

Using Tailwind’s @apply directive, we extract repeated patterns and define a custom singular CSS “component” class. By doing this, we only write the long list of utility classes once in our code. We also successfully move our utility class names to their rightful place: a stylesheet.


    .fancy-button {
      @apply w-full sm:w-auto flex-none text-white text-lg leading-6 font-semibold py-3 px-6 border border-transparent rounded-xl transition-colors duration-200;

      &:hover {
        @apply bg-gray-700;
      }

      &:focus {
        @apply ring-2 ring-offset-2 ring-offset-white ring-gray-900 outline-none;
      }
    }

Let’s take a look at our new button in use:


    // Before
    <a class="w-full sm:w-auto flex-none bg-gray-900 hover:bg-gray-700 text-white text-lg leading-6 font-semibold py-3 px-6 border border-transparent rounded-xl focus:ring-2 focus:ring-offset-2 focus:ring-offset-white focus:ring-gray-900 focus:outline-none transition-colors duration-200">
      Get started
    </a>

    // After
    <a class="fancy-button">
      Get started
    </a>

With a bit of configuration, we are back in control

CSS has always put us in the driver’s seat regarding the naming of our classes, and with a bit of configuration, Tailwind CSS can do that as well. With this small example, we have proven that you don’t have to make a mess of your DOM with too many class names when using a utility-first CSS framework like Tailwind CSS. If you were on the fence, I hope this sheds some light on why many front-end designers enjoy working with Tailwind.