Design Systems with Storybook, v2

Using clsx to Compose Class Names

Steve Kinney

Steve Kinney

Design Systems with Storybook, v2

Check out a free preview of the full Design Systems with Storybook, v2 course

The "Using clsx to Compose Class Names" Lesson is part of the full, Design Systems with Storybook, v2 course featured in this preview video. Here's what you'd learn in this lesson:

Steve adds the Destructive variant to the story. Then the clsx library is used to compose class names for components. The base button styles, variant, and any additional custom classes are passed to the clsx method, and a string with those class names is returned.


Transcript from the "Using clsx to Compose Class Names" Lesson

>> All right, so our job was to add a destructive button. Again, and that's what makes us kinda really cool here is that button will be passed to all the children. The word button, in this case, is passed to all of them. So really, all I have to do is write what makes this unique, which is destructive.

Nope, I want to hit a capital D, story, and then there was the arts variant is destructive. Cool, so we've got that in place and I should be able to flip over and there's my destructive button, so on and so forth. It does keep some stuff in memory, cool.

So we've got all of those in place as well. Before I take a tour of this top bar up here, I do wanna refactor this a little bit. I'm gonna show you a library that is my starter kit templates. It's a library that I include, because I use it all the time, which is a lot of times, especially as our components get more complicated, we do some amount of composing of that class string, right?

It's like okay, add this class if they pass this prop. For instance, small, medium and large, that's gonna be a different set of classes that I'm gonna programmatically add or remove. And you can play a game called Fun with Ternaries and string interpolation. And I mean, you will still be playing some version of Fun with Ternaries.

But then you end up with extra spaces in your class name, and you're learning parts of why I am very anxious. I don't like that, I like the ability to have some amount of programmability. This is a utility, you can npm install. It's actually included in this repo already that you could write yourself, but you don't have to because it exists, and this library is called clsx.

And there are some other ones, I think there's one called CX. And all it does is a little utility library for composing class names. Cuz right now, I have to remember to add the space in there as I'm concatenating different things on. And with only one unique property to this button, that's not the big deal, right?

But you can imagine that eventually I'm gonna cry. And so all this does, I'll kinda write just some pseudo code real quick up here, then we'll delete it, is that it will take some class names, foo bar, whatever. And then you can either do it as one string, you can give it as many arguments as you want, you can give it an array.

But more importantly, anything that evaluates the falsy will not be included. So if I said something like, variant is secondary and then put secondary, right, or something along those lines, right, those will not be included if those don't evaluate the truth. You can use a ternary, you can use functions, you can use whatever.

It will effectively let you programmatically compose a class. So I'm gonna refactor this code a little bit, and we're gonna say that let class name and we'll say clsx is initially those button styles. And we'll say, I can do this a bunch of different ways. You get to choose which one makes you happy.

You can say something like variant = = secondary, and then you do &&, styles. And we do .secondary. And you:re gonna do the same thing for destructive. Variant === destructive, and let's say that real quick, and we'll say && styles.destructive, right? This will do the trick and we can verify that.

Again, this is why I like developing with this. But happens to be that the way that CSS modules work this is as you can almost tell an object where the keys are the class names. And because you saw when I refactor that code instead of make it button dash secondary button dash destructive, now you know why.

I could theoretically also do something like this. Right, and that will theoretically get me secondary and destructive as well. Could I have made a primary class sharp? But because that will become undefined, it won't be included in there. So we get the same idea and so you can see how this becomes a really nice way to do this.

One pattern that this is a choice time on if you wanna do this, right? And we've gone back and forth on our team. And my official stance on this is for very primitive components, go for it and maybe you don't do it for more complicated ones, he says, before he tells you what it is, which is do you want the consumer of this button to be able to pass in additional classes, right?

Cuz right now, we have a problem with they pass in classes since that comes after, then theoretically, they will override even my default classes. So one way to solve for that, it's just an order of operations issue, right? Now, these class take precedents, right, but if you want to let somebody pass in classes, right, you can do that.

Why do you wanna do that? The reason you do and do not wanna do that are the same reason, why you would wanna let somebody do that. Okay, well, they need to just like add some extra padding or some little edge case. It's nice to give them that ability, right, to pass in just a few extra classes for some amount of spacing or create another color.

Now, think about the worst person on your team. Probably you. The one that writes the most reckless code, right, and know that they are going to do that rather than update the component and you will eventually regret it, right? And that's why I say for very simple components, maybe you choose to do it.

We currently do this trick and then I think we kinda had the talk of maybe we stopped doing this. But I cannot say it's always a bad idea or always a good idea. This is a kind of it depends situation. So it's the ability to let them pass on a class name.

So what we can do here is we can separate out class name as one of the props that we take in. And now that we've done this, we've got the issue where it's duplicated, so we'll fix that in a second. The order doesn't matter anymore, cuz it's no longer in that prop spread operator, cuz we pulled it out.

So this is the rest of everything. So we're taking out variant class name, this is all the other props. Now, order doesn't matter. You can put in whatever order you want. And what we can do here is we can actually put it into the CLSX function, right, and just say, Class name, and here the order does matter.

We'll call this one classes, right, I don't know. It can't be class, cuz of JavaScript. So now, basically, you've got your default styles, right, for the primary button. Then you have any kind of overrides for the secondary destruction button, then you have whatever they pass in, right? And this means that if they wanna add some extra margins to the button or what have you, they can go ahead and do that, right?

If you don't that, don't do it. We're on the fence both ways. I can argue for it, I can argue against it, but this is a way to kind of let, at least the primitive components be extensible. I would say that for the kinda higher order components that build out of multiple kind of atoms and stuff like that, you might not choose to allow that, especially, cuz a great example is we have this component called input with label and all the different accoutrements.

So then where does the classes go? They go on the input, they're going to container, they're going to label, right? And there's no right answer, that all answers to that question are wrong, cuz [LAUGH] I have done them all and regretted each and every one of them. So this is a pattern you can choose to use, choose not to use.

Our current philosophy on my team is that we're pretty sure it's a bad idea because, again, then it becomes really clear to know if you make a change in the design system, is it consistent across your code base or not, because somebody could have added a class, right?

And so I'm not gonna argue that we should do that, but I want to expose you to it if you have a use case or you're actually in the way that we're using these things it is really important. I would advise though only on the lowest level doesn't make a ton of sense.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now