How to handle classes in large components with Tailwind and React ?
8 min read / January 16, 2023
As a front-end developer, one of my goals on every project is to have clean, concise and easily maintainable code. And to be honest, with Tailwind it's sometimes very difficult to achieve this goal, especially when creating large components with multiple variations and states.
But don't get me wrong, I love working with Tailwind ❤️ and have been using it on all my projects since its release, I actually built this blog with it!
Let's get back on topic though and take the example of a simple button, this component is going to be visually different depending on its variant
, it can also be disabled
and have different sizes. All these props will generate a lot of classes, resulting very quickly in a big mess in our code 🤯, especially if we want to combine props or add others later on.
What about the classnames library ?
The package classnames is excellent, and partially solves this problem by allowing us to split our Tailwind classes within our component. I used this package for a long time and was happy with the logic I had in place to organize my classes.
Let's look at the simplified example of my design system button created with classnames
, try changing the variant
, size
and disabled
props :
As you can see, the above code works perfectly, and to be honest, it's already clean, well organized and easily maintainable.
However, several details can be improved:
- The conditional syntax in the
classnames
function is quite cumbersome, especially if we add more props. - We have to manually assign classes to the props, and manually type the visual aspects of our component
- The typing of our
classes
object must be added but will be tedious to set up and will duplicate our interface defined above
Can't we just get Stiches but for Tailwind 🙏 ?
Some of you know or use Stiches, a CSS-in-TS library that offers a very nice DX, and allows us to better organize the variants of our components as well as to combine and type them automatically: 🤩.
Unfortunately with Tailwind we can't use Stiches... Or maybe we can 🤔 ?
Getting started with cva
Let's convert our code with cva! First let's install the yarn add class-variance-authority
library. cva
, like Stiches, is based on the creation of variants and the composition of them. The structure I had in place before was already partly based on this principle, so it's quite simple to convert it with cva
.
We have the same result as before, but with one detail: our code is much more organized and clean! Let's explain in detail what we have done.
In our Button.styles.ts
file we have replaced the classes
object with a container that uses the cva
function. This function accepts two arguments: the first one is our base classes, i.e. the ones that will be present on our button component regardless of the variant. The second argument is an object made of three objects that I will explain in more detail.
Variants
In the variants
object I define all the variants of my component: variant
, size
and disabled
.
As you can see, for our variant
props I define very few classes because I don't want to have class conflicts when my button is disabled
or not. The rest of the classes will be added in our next coumpoundVariant
object.
Compound variants
It's within the compoundVariants
object that I place the majority of my classes. Here for example I only want to apply my classes according to my variant
and disabled
props, and as said earlier, I don't want them to conflict, so I define them separately.
Default variants
Finally the defaultVariants
object allows me, as its name indicates, to specify which variants will be applied by default :
Automatic typing
As you may have noticed, our types have been switched from manual to automatic input using cva
and its VariantProps
type. We just need to extend the type with the constant we created just now VariantProps<typeof buttonStyles>
. So my interface looks like this now:
Automatic class association
The biggest improvement we made is the automatic typing. We went from a manual props - classes association to an automatic association thanks to cva
and the constant we created above, which gives us :
Conclusion
Using cva
actually allows us to have a single source of truth for our styles, props, and types, making our code much more readable, maintainable, and offering a DX similar to what we find with Stiches.
Here we covered the basics of cva
and used a fairly simple example, to get a feel for the concepts of the library, but trust me, it's when you use it on large components that you'll fall in love with the package and won't be able to live without it!
Hats off to Joe Bell and all the contributors who maintain and add more and more features to cva
🎉 !