DEV Community

loading...
Cover image for Clean TailwindCSS with React

Clean TailwindCSS with React

smcalilly profile image Sam McAlilly ・Updated on ・3 min read

TailwindCSS's utility classes help to make attractive and user-friendly websites, quickly. React helps to make reusable UI components. Tailwind + React, gonna be some fun.

But how do you create reusable React components with Tailwind, cleanly? I don't know. I'm trying something like the following. Let me show you (the TLDR is at the end).

DisplayCard

This is a simple example. We're making a <div> but wrapping it as a React component and naming it DisplayCard.

import React from 'react'

function DisplayCard({ className, children }) {
  return (
    <div className={className}>
      {children}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

We style it with Tailwind and use it like this:

<DisplayCard className='flex flex-col items-center justify-center bg-blue-300 border border-gray-300 rounded-lg w-8 h-12 p-4'>
  <h3>Heads up!</h3>
  <p>Some things you need to know.</p>
</DisplayCard>
Enter fullscreen mode Exit fullscreen mode

Custom Utility Classes

with HTML

Thanks to Tailwind's custom utility classes, it might be an unneccessary abstraction to create a DisplayCard React component only to wrap an HTML div. With Tailwind, you could simply add a custom utility class:

/* tailwind.css */
.display-card {
  @apply flex flex-col items-center justify-center bg-blue-300 border border-gray-300 rounded-lg w-8 h-12 p-4
}
Enter fullscreen mode Exit fullscreen mode

And instead of a DisplayCard component, you can render a plain ol' <div> with the custom utility class:

<div class="display-card">
  <h3>My Sunken Ship</h3>
  <p>We're lost, you need to know.</p>
</div>
Enter fullscreen mode Exit fullscreen mode

with React

A plain ol' <div> works most of the time. But with React, you'll often want to create some custom components to reuse throughout your app.

For instance, your code might need our reusable DisplayCard component. Now it's styled consistently with the custom utility class:

<DisplayCard className='display-card'>
  <h3>Forlorn Status</h3>
  <p>Searching, looking for an answer.</p>
</DisplayCard>
Enter fullscreen mode Exit fullscreen mode

This is pointless?

If you already know Tailwind, this is pointless up to this point, but this isn't what I mean by doing Tailwind + React cleanly.

What if we want to use DisplayCard with some default styles and perhaps add more styles, based on the context of where the component is used?

Easy, maybe? We could do something like this:

const defaultClass = 'display-card'

// need to add a margin to the top
<DisplayCard className={`${defaultClass} mt-8`}>
  <h3>My Display Card</h3>
  <p>Some things you need to know.</p>
</DisplayCard>

// this is used in a different part of the site, with padding & not margin
<DisplayCard className={`${defaultClass} p-32`}>
  <h4>Uh,</h4>
  <p>idk, hang the display card somewhere near the bottom of the page. just tell them they need to <a href="/black-hole">click here</a>.</p>
</DisplayCard>
Enter fullscreen mode Exit fullscreen mode

That's kinda clunky, though...

Encapsulate the utility class

Our generic DisplayCard component requires some default styles but might also sometimes need additional styles.

So first, we should encapsulate the Tailwind custom utility class display-card within the DisplayCard component.

function DisplayCard({ children }) {
  const defaultClass = 'display-card'

  return (
    <div className={defaultClass}>
      {children}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Next, we need to somehow add the additional classes to the component. How should we establish a default class and allow the possibility for some additional classes? Maybe something like this:

function DisplayCard({ className, children }) {
  const defaultClassName = className ? `${className} display-card` : 'display-card'

  return (
    <div className={defaultClassName}>
      {children}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Finally

That can get kind of messy, though. So maybe we could create a helper function? Let's try:

function makeClasses(defaultClassName, additionalClasses){
    if (defaultClassName === undefined) {
        throw Error('makeClasses function must have a string argument with default classes.')
    } else {
        return additionalClasses ? `${additionalClasses} ${defaultClassName}` : defaultClassName
    }
  }

function DisplayCard({ className, children }) {
  const defaultClassName = makeClasses('display-card', className)

  return (
    <div className={defaultClassName}>
      {children}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

So now we can use our DisplayCard component with our custom display-card class encapsulated as the default style:

<DisplayCard>
  <h3>My Display Card<h3>
  <p>Some things you need to know.</p>
</DisplayCard>
Enter fullscreen mode Exit fullscreen mode

And then, it's easy to re-use the DisplayCard in a different context while still retaining the default display-card style:

<DisplayCard className='m-8'>
  <h3>My Display Card<h3>
  <p>Some things you need to know.</p>
</DisplayCard>
Enter fullscreen mode Exit fullscreen mode

With one last refactoring (it might be ill-advised?):

// go crazy with it, don't even assign a variable
// just pass the makeClasses function directly to the div?

function DisplayCard({ className, children }) {
  return (
    <div className={makeClasses('display-card', className)}>
      {children}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Feedback

I don't know if that's the best approach...it's just something I've been toying with. If this breaks any rules of React or Tailwind or programming in general, or if I have wrong syntax or code that doesn't work, or if the writing doesn't make sense and you need some clarity, please let me know in the comments.

Discussion (13)

pic
Editor guide
Collapse
christianblos profile image
Christian Blos

No need for the makeClasses() function. This works even if className is undefined:

function DisplayCard({ className, children }) {
  return (
    <div className={`display-card ${className}`}>
      {children}
    </div>
  )
}

Since you have a DisplayCard component I would just use the tailwind classes directly in the component instead of creating a display-card class. Feels redundant to me and you would probably only use it in this component anyway.

Collapse
smcalilly profile image
Sam McAlilly Author • Edited

If className is undefined, won't it add "undefined" to the string? Pretty sure I saw that behavior when I was first using Tailwind in React and that's what led me to the makeClasses() function. (I don't know if I was using string interpolation though?) I would inspect the DOM elements in my dev tools and see HTML on the DOM like this:

<div class="mt-8 undefined">
</div

I don't like that so I started using the ternary operator whenever creating default styles in my reusable components. Then I refactored the ternary operator into a helper function, which I've named makeClasses().

Also, you're totally right this is redundant to create the DisplayCard component. I hinted at that in my post, when I said that you can just use a div instead of creating a React component. I thought the component was a simple abstraction to illustrate how to make reusable components with Tailwind, cleanly.

Collapse
christianblos profile image
Christian Blos

Oh, right. Here without undefined :)

className={`display-card ${className || ''}`}

I personally prefer this so the component doesn't depend on a helper function.

I know, I just wanted to point out that I would also create a reusable react component, but the display-card css class is not necessary in this case.

Thread Thread
smcalilly profile image
Sam McAlilly Author

oh cool, i didn't know you could do a || in string interpolation like that. today i learned, thanks

Collapse
jvarness profile image
Jake Varness

+1 to string interpolation.

Collapse
nelsonfrank profile image
Nelson Frank

Nice article @sam , i have been using tailwindcss and react for sometimes now in different projects, i learn another new thing from this article, Thank you...

Collapse
smcalilly profile image
Sam McAlilly Author

glad i could help !

Collapse
zikrimarquel profile image
Marquel

Will this method works with purgecss?

Collapse
smcalilly profile image
Collapse
mrniceknight profile image
mrniceknight

my only gripe with this method is if the default class has 'm-10' and you overwrite it with 'm-5', the resulting className would be 'm-10 m-5'

Collapse
smcalilly profile image
Sam McAlilly Author • Edited

After more thought and reading some more about default styles, I would argue that you're not supposed to be putting margin into a reusable component. That's kind of the point of this approach, because that sort of styling is based on the "context" of where you're using it. One quote I read recently said something like, "Putting margin on a reusable component is like adding glue before you know where you're gonna put it."

Collapse
smcalilly profile image
Sam McAlilly Author

oh yeah, great bug catch.