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>
)
}
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>
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
}
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>
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>
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>
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>
)
}
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>
)
}
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>
)
}
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>
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>
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>
)
}
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)
No need for the
makeClasses()
function. This works even if className is undefined: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.
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 themakeClasses()
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: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 adiv
instead of creating a React component. I thought the component was a simple abstraction to illustrate how to make reusable components with Tailwind, cleanly.Oh, right. Here without undefined :)
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.oh cool, i didn't know you could do a
||
in string interpolation like that. today i learned, thanks+1 to string interpolation.
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...
glad i could help !
Will this method works with purgecss?
yep
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'
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."
oh yeah, great bug catch.