January 07, 2022

Using CSS variable in Tailwind CSS and dynamic Tailwind class

Frontend
Tailwind CSS, React.js, Frontend

Dynamic class?#

Let say if you want a custom class that is not predeclared in Tailwind, like a width of 123px, and that's absolutely possible with the JIT engine, just do w-[123px] and it will generate this custom class for you at compile time. However, if you want to do something "weirder", such as a dynamic width class base on some logic your wrote, you want Tailwind to handle it, and you did something like:

// JSX
<input className={`w-[${widthState}]`} />

You found that class is in the HTML from the browser dev panel, but the style is not applying, and there's no way to can find the according CSS class in the stylesheet. That's because Tailwind doesn't generate styles from such dynamic string. Tailwind only accepts static strings (please correct me if I'm wrong on this).

So... dead-end? Actually... No!

CSS Variable#

Modern browsers can render CSS styles from CSS variables with the var() function. You can declared a CSS variable like this --my-width: 123px in you stylesheet, and you can reuse this variable in the following manner: width: var(--my-width);. That's very useful when you're applying the same style to many CSS classes, you don't need to update them one by one when you want to change the style value.

So, how is this useful to the problem?

Well, with Tailwind 3.0, you can apply the custom CSS variable using the JIT engine. You will need to do something like this: w-[var(--my-width)]. That's awesome! But there's a problem, don't we still declare this variable statically?

Usually, you'll declare this variable in your stylesheet --my-width: 123px; or with the Tailwind's JIT engine, you can also do something like [--my-width:123px], or [--my-width:_123px] if you want to use space, but you have to replace it with an underscore.

But HEY!? That still doesn't solve the problem.

Inline styles#

Yes, that still doesn't solve the problem. That's why this trick came to rescue. Which is declaring the CSS variable in the style property:

// TSX
// You don't need `as React.CSSProperties` if you're not using React with TypeScript
<input style={{"--my-width": widthState} as React.CSSProperties} className="w-[var(--my-width)]" />

The example above is in React.js, but you should be able to do the same approach in any other frontend frameworks. The w-[var(--my-width)] will be generated into a Tailwind class without any problem, if you want to change the width, you just need to change the value of the widthState variable/state. Now you got a Tailwind class that can handle dynamic values.

Another amazing thing is that this inline --my-width CSS variable can only be access in this element and its descendants only. You can't use it on a sibling element, the sibling element can also declare the variable with the exact same name, they won't affect each other. You can also override the value on a descendant element and it won't affect the original variable from the parent. Here's an example I created in Tailwind Play

What's the point? Can't you just do style={{width: widthState}} so you don't need to create an extra Tailwind class that does the exact same thing?

And yes, here's why. If you just need a dynamic width like the example above, then sure, that's redundant code, you don't have to do that. But if you want to do something more than that with the class, such as using it with some Tailwind prefixes, like hover:w-[var(--my-width)]. Then it is very handy, because you can't do hover styles inline. So this trick also give you a way to write inline hover styles, or responsive styles, and many more...