Simple Trick to Clean Up Tailwind CSS in React

3 min read

Problem

A pain point with using utility-first CSS framework like Tailwind CSS is that you inevitably end up with components such as these.

<a
  href="#"
  class="w-full flex items-center justify-center px-8 py-3 border
  border-transparent text-base leading-6 font-medium rounded-md text-primaryBold
  bg-neutralBg hover:text-primary focus:outline-none
  focus:border-primaryBgSofter focus:shadow-outline-indigo transition
  duration-150 ease-in-out md:py-4 md:text-lg md:px-10"
>
  Live demo
</a>

Consider the following:

What are the ways we would write Tailwind CSS utility classes?

It could be a string.

function Button() {
  return (
    <button className="px-4 py-2 bg-blue-500 hover:bg-blue-600 shadow-md ...">
      Click
    </button>
  );
}

To include JavaScript, we could write a template literal.

function Button({color}) {
  return (
    <button
      className={`${whateverJavaScript(color)}
    px-4 py-2
    bg-blue-500 hover:bg-blue-600
    shadow-md
    ...
    `}
    >
      Click
    </button>
  );
}

The problem with template literals is in the name itself.

They are literal.

Formatters and linters such as Prettier and ESLint make no attempt to format the utility classes written in template literals, leading to files with sloppy indentation.

Solution

The solution is inspired by code in tailwindlabs/tailwindui-react.

The solution is a function.

JavaScript:

tailwind.js
export function tw(...classes) {
  return classes.filter(Boolean).join(' ');
}

TypeScript:

tailwind.ts
export function tw(...classes: (false | null | undefined | string)[]): string {
  return classes.filter(Boolean).join(' ');
}

We could optionally name this function classNames as the original author Robin Malfait did in tailwindui-react.

I chose tw to give brevity for the writer and context for the reader.

Now we can cleanly span our classes over many lines.

Moreover, now the formatter can easily format our files.

components/Hero.tsx
import Logo from '@assets/logo.svg';
import tw from '@utils/tailwind';

function Hero() {
  return (
    <Logo
      className={tw(
        'relative',
        'w-full h-auto',
        'mt-2 mb-4',
        'text-blue-600 fill-current',
      )}
    />
  );
}

Group together similar classes, such as

  • position
  • spacing
  • sizing
  • color

When in doubt, group things the way Tailwind groups things on their website.

components/Hero.tsx
import Logo from '@assets/logo.svg';
import tw from '@utils/tailwind';

function Hero() {
  return (
    <Logo
      className={tw(
        'absolute top-2 left-2', // position
        'mt-2 mb-4', // spacing
        'w-full h-auto', // sizing
        'text-blue-600 fill-current', // color
      )}
    />
  );
}

Easily include JavaScript sources.

components/Hero.tsx
import Logo from '@assets/logo.svg';
import tw from '@utils/tailwind';
import globalStyles from '@styles';

function Hero() {
  return (
    <Logo
      className={tw(
        'absolute top-2 left-2', // position
        'mt-2 mb-4', // spacing
        'w-full h-auto', // size
        'text-blue-600 fill-current', // color
        globalStyles.transitions,
      )}
    />
  );
}

In addition, we could use a plugin like Headwind to also alphabetize the classes.

I don't care that much to have my classes alphabetized, and I've found it to make for a worse DX.

Hope this helps you clean up Tailwind CSS in your own React projects.

What are some of your favorite Tailwind CSS tricks?

Let me know on Twitter! 👋