Using Javascript Variables in Tailwind
Tailwind is a CSS utility framework that provides shorthand CSS classes for rapidly composing designs. Instead of writing CSS rules, you simply add utility classes to your element. These pre-made classes are handy, but sometimes you'll need to use some data that's user-generated or only available in Javascript to generate your styles. There are a few approaches, but there's one that follows best practices most closely.
I'll start with some context on why this problem exists, and how Tailwind works. If you don't want to read that, you can skip to the solution instead!
This blog post is the first in a series about using Tailwind at enterprise scale. I'll cover tips and tricks I've developed while building a component library at Kizen while migrating to Tailwind from a css-in-js approach.
Let's get into it! Take this simple element and some styling:
<style>
.box {
width: 3.5rem;
height: 3.5rem;
background-color: rgb(239, 68, 68);
}
</style>
<div class="box"></div>
html
In Tailwind, using the shorthand utility classes, the same element can be written as:
<div class="h-14 w-14 bg-red-500"></div>
html
For reference, here's how that element looks using either approach:
My examples from this point on will use React, but the same principles apply to any framework.
A Quick Look at How Tailwind Works
Tailwind provides many utility classes that can be used to style elements, mirroring almost all the existing CSS properties. You wouldn't want to include all of these classes in your app if they aren't used, though, so the Tailwind CLI parses your code and generates a CSS bundle of only the utility classes you're using.
In my first example, I used the token red-500
for the color. This yields some generated CSS:
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
css
This particular class was only included in the bundle because it was used in the code.
For example, bg-red-400
will not be generated or bundled unless it's added somewhere in the application.
That's great if we're using a predefined color pallete, but that's not always the case. Tailwind also allows for arbitrary values, using a special notation (note the square brackets, and lack of spaces inside the rgb color value):
<div className="bg-[rgb(239,68,68)]" />
jsx
Like before, the Tailwind CLI will parse this out and generate a bundle with the correct class. In this case, it looks like this:
.bg-\[rgb\(239\2c 68\2c 68\)\] {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
css
This looks great, and works because the className is static and can be easily parsed. However, this approach starts to break down if you're using content from another system or storage medium, like user-defined colors.
Let's look at another contrived example where this exact issue comes to light. In this one, we'll have a user-defined color stored in a variable, and we want to use that as the background:
const color = 'rgb(239, 68, 68)';
javascript
Can we simply build the className dynamically, like this?
<div className={`bg-${color}`} />
jsx
❌ This approach won't work, because the Tailwind CLI can't statically determine what that class will be, so it doesn't get included in the bundle. This will have no effect on our UI, and the color won't be applied.
Common Solution
One of the most common solutions I've seen is to use the style
attribute instead of the className
attribute:
<div
className="h-14 w-14"
style={{
backgroundColor: color,
}}
/>
jsx
⚠️ This does work, but I don't particularly like this approach. Not only are we breaking out of Tailwind's utility classes, reducing maintainability and increasing surface area for bugs, but we're also introducing inline styles, which can be very difficult to override because they take the highest precendence.
There has to be a better way to pass arbitrary values to Tailwind, that will allow us to keep all our styling limited to a single approach.
Using The Style Prop the Right Way
We're on the right track here, because the solution does involve inline styles, but in a different way
that avoids all the problems I listed above. The solution is to use the style
attribute to set a CSS
variable, that we can then access from Tailwind.
<div
className="h-14 w-14 bg-[var(--user-color)]"
style={{
'--user-color': color,
}}
/>
jsx
Here, I'm using the same square-brace notation as earlier, but instead of an rgb color, I have the CSS variable referenced.
Let's look at the generated CSS to see why this works:
.bg-\[var\(--user-color\)\] {
background-color: var(--user-color);
}
css
✅ We can statically analyze and parse the className, so no issues there! And, we're using a CSS variable, rather than passing rules as inline styles, so we don't end up with split strategies for our styling, or precendence issues.
The inline style is only used to set the CSS variable, which is then used in the Tailwind class! Since the generated CSS doesn't need to know the value of the variable, it doesn't matter that it's dynamic.
Conclusion
This pattern isn't limited to just colors! This can be used for any CSS property that has a Tailwind utility class. I've used this approach in conjunction with calculations for padding, margin, and even font sizes! If you want to learn more about arbitrary styles in Tailwind, check out the Tailwind docs.
This blog post is the first in a series I'll be writing about using Tailwind in enterprise-scale applications, and some of the problems we've had to solve at Kizen as we migrate from a CSS-in-js approach to Tailwind.
Stay tuned for more under the css and Tailwind tags on my blog, and if you have any topics about Tailwind at scale you'd like covered, shoot me an email at tailwind@k10y.com!