skies.dev

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="text-primaryBold bg-neutralBg hover:text-primary focus:border-primaryBgSofter focus:shadow-outline-indigo flex w-full
  items-center justify-center rounded-md border border-transparent px-8
  py-3 text-base font-medium
  leading-6 transition duration-150
  ease-in-out focus:outline-none md:py-4 md:px-10 md:text-lg"
>
  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="... bg-blue-500 px-4 py-2 shadow-md hover:bg-blue-600">
      Click
    </button>
  );
}

To include JavaScript, we could write a template literal.

function Button({color}) {
  return (
    <button
      className={`${whateverJavaScript(color)}
    ... bg-blue-500
    px-4 py-2
    shadow-md
    hover:bg-blue-600
    `}
    >
      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.

Hey, you! 🫵

Did you know I created a YouTube channel? I'll be putting out a lot of new content on web development and software engineering so make sure to subscribe.

(clap if you liked the article)

You might also like