A simple strategy for structuring TailwindCSS classnames
Tags: tailwindcss, javascript, react, css
Published: 2021-04-13 19:02:15

This is the third article of my small series about TailwindCSS. If you have not done so already, check out my other posts.

Anyone who has proposed to use TailwindCSS for their project has probably heard something like this:

> "Ugh, this looks like inline styles!"

> "How should I keep an overview with so many classnames in my component?"

> "This seems hard to maintain…"

Yes, I understand these concerns. With Tailwind's utility-first approach, the default procedure is to write any utility-classname directly into the component's markup. With more complicated components this can quickly come out of hand.

In today's post, we will look at a possibly better solution which I am using for my projects for a while now.

## A simple example

Let's take this `Navigation` component as an example:

```jsx
const Navigation = ({ links }) => {
  const router = useRouter()
  return (
    <nav className="container">
      <ul className="flex flex-col justify-end list-none sm:flex-row">
        {links.map((link, index) => {
          return (
            <li
              key={index}
              className="mb-3 sm:ml-3 sm:mb-0 even:bg-gray-50 odd:bg-white"
            >
              <a
                className={`text-black font-bold inline-block rounded-full bg-yellow-400 py-1 px-3 ${
                  router.pathname === link.path
                    ? 'text-white'
                    : 'hover:bg-yellow-500'
                }`}
                href={link.path}
              >
                {link.name}
              </a>
            </li>
          )
        })}
      </ul>
    </nav>
  )
}
```

What can we do to not let the component look so messy?

My first rule of thumb is: Do any calculations before your render / return function and only use these calculated flags in your render. That applies for the `router.pathname === link.path` condition – let's move it into a `const` and name it `isActive`.

And while we are at it, let's move the `className` definitions to `const`s as well – just name them after their according HTML element (another reason for using semantic elements instead of a bunch of `div`s ;)):

```jsx
const Navigation = ({ links }) => {
  const router = useRouter()
  const navClassNames = 'container'
  const listClassNames = 'flex flex-col justify-end list-none sm:flex-row'
  return (
    <nav className={navClassNames}>
      <ul className={listClassNames}>
        {links.map((link, index) => {
          const isActive = router.pathname === link.path
          const listItemClassNames =
            'mb-3 sm:ml-3 sm:mb-0 even:bg-gray-50 odd:bg-white'
          const anchorClassNames = `text-black font-bold inline-block rounded-full bg-yellow-400 py-1 px-3 ${
            isActive ? 'text-white' : 'hover:bg-yellow-500'
          }`
          return (
            <li key={index} className={listItemClassNames}>
              <a className={anchorClassNames} href={link.path}>
                {link.name}
              </a>
            </li>
          )
        })}
      </ul>
    </nav>
  )
}
```

That looks better already, but there’s still room for improvement.

## Use `.join(" ")`

Instead of writing long strings of classNames, let's write arrays and concatenate them automatically. The good thing about arrays is that you can also add entries conditionally – and therefore get rid of the template literal condition:

```jsx
const Navigation = ({ links }) => {
  const router = useRouter()
  const navClassNames = 'container'
  const listClassNames = [
    'flex',
    'flex-col',
    'justify-end',
    'list-none',
    'sm:flex-row',
  ].join(' ')
  return (
    <nav className={navClassNames}>
      <ul className={listClassNames}>
        {links.map((link, index) => {
          const isActive = router.pathname === link.path
          const listItemClassNames = [
            'mb-3',
            'sm:ml-3',
            'sm:mb-0',
            'even:bg-gray-50',
            'odd:bg-white',
          ].join(' ')
          const anchorClassNames = [
            'text-black',
            'font-bold',
            'inline-block',
            'rounded-full',
            'bg-yellow-400',
            'py-1',
            'px-3',
            isActive ? 'text-white' : 'hover:bg-yellow-500',
          ].join(' ')
          return (
            <li key={index} className={listItemClassNames}>
              <a className={anchorClassNames} href={link.path}>
                {link.name}
              </a>
            </li>
          )
        })}
      </ul>
    </nav>
  )
}
```

*(One note concerning the ternary operator that conditionally adds a className: If you don't have an either/or operation, just add an empty string to the else case (e.g. `isCondition ? 'myClass' : ''`) and don't rely on shorthands like `isCondition && 'myClass'`. The latter would work for `undefined` values but add a `"false"` string to your array in case the condition is false.)*

## Abstract all component styles into a `styles` object

Let's further work on this approach: In this example with multiple elements in one component especially it might make sense to create a styles object outside of the component's `return` functions.

But there is one issue: In our anchor link styles definition we rely on having access to the `isActive` flag. We can easily solve this by transforming its definitions from a string to an arrow function returning a string. With such a function you can provide any condition you need in the scope of your element's styles array:

```jsx
const styles = {
  nav: 'container',
  ul: [
    'flex',
    'flex-col',
    'justify-end',
    'list-none',
    'sm:flex-row',
  ].join(' '),
  li: [
    'mb-3',
    'sm:ml-3',
    'sm:mb-0',
    'even:bg-gray-50',
    'odd:bg-white',
  ].join(' '),
  a: ({ isActive }) =>
    [
      'text-black',
      'font-bold',
      'inline-block',
      'rounded-full',
      'bg-yellow-400',
      'py-1',
      'px-3',
      isActive ? 'text-white' : 'hover:bg-yellow-500',
    ].join(' '),
}

const Navigation = ({ links }) => {
  const router = useRouter()
  return (
    <nav className={styles.nav}>
      <ul className={styles.ul}>
        {links.map((link, index) => {
          const isActive = router.pathname === link.path
          return (
            <li key={index} className={styles.li}>
              <a className={styles.a({ isActive })} href={link.path}>
                {link.name}
              </a>
            </li>
          )
        })}
      </ul>
    </nav>
  )
}
```

Another note here: I've put the flag into an object instead of directly into the arguments list (`({ isActive })` instead of `(isActive)`). This makes sense because it is easier to maintain: Otherwise you would have to think of the particular order of your flags in both the function call and its definition within the styles object. With the object's destructuring syntax you can work around this issue and don't need to worry about the object entries' positions – by just adding two more characters.

## Put styles into a separate file

I you want to take it even further, you could outsource your styles to a separate file with the same approach:

```js
// Navigation.styles.js
export default {
  nav: 'container',
  ul: [
    'flex',
    'flex-col',
    'justify-end',
    'list-none',
    'sm:flex-row',
  ].join(' '),
  li: [
    'mb-3',
    'sm:ml-3',
    'sm:mb-0',
    'even:bg-gray-50',
    'odd:bg-white',
  ].join(' '),
  a: ({ isActive }) =>
    [
      'text-black',
      'font-bold',
      'inline-block',
      'rounded-full',
      'bg-yellow-400',
      'py-1',
      'px-3',
      isActive ? 'text-white' : 'hover:bg-yellow-500',
    ].join(' '),
}
```

```jsx
// Navigation.jsx
import styles from "./Navigation.styles";

const Navigation = ({ links }) => {
  const router = useRouter()
  return (
    <nav className={styles.nav}>
      <ul className={styles.ul}>
        {links.map((link, index) => {
          const isActive = router.pathname === link.path
          return (
            <li key={index} className={styles.li}>
              <a className={styles.a({ isActive })} href={link.path}>
                {link.name}
              </a>
            </li>
          )
        })}
      </ul>
    </nav>
  )
}
```

I'm working with this approach for a while now and I really like it. It's simple and clean and it allows me to write TailwindCSS without cluttering my components with a bunch of classnames.

## Other approaches

There are some other approaches that you can use instead or in combination with the above:

### Use `classnames()` (or `clsx()`)

The `classnames()` library is a simple utility to concatenate your classNames into a string. It has some additional functions built in that might come in handy.

`clsx()` has the same API but comes with a smaller bundle size:

- https://github.com/JedWatson/classnames
- https://github.com/lukeed/clsx

These libraries makes sense especially when dealing with many conditions like the `isActive` one in the example above or with nested arrays that you would need to flatten otherwise.

For most cases I'd say that joining an array like above will do the work and that you don't need any additional package for that – but for bigger projects it might make sense to embrace the API of those libraries.

### brise

Another interesting approach is pago's brise:
- https://github.com/pago/brise

It is using template literals to work with Tailwind styles. And it even allows you to add custom CSS by using emotion's `css` utility.

It's also definitely worth checking out.

I hope this post inspired you writing cleaner components when using TailwindCSS. If you have any other recommendations feel free to add them to the comments!