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

The "Adding Variants" 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 creates variants for the button component. A ButtonProps type is defined to create a union type for the available variant values. The Storybook variants are updated to use the correct variant class.


Transcript from the "Adding Variants" Lesson

>> Okay, so what we're gonna do is like start to think about this idea of a variant in this case. And so that starts on my end with a little bit of type script to make sure that we are actually getting what we think, and I stop clicking on the badge in this case, okay?

So, in React and typescript, I can kind of create a type for the button props. I'll say type ButtonProps. And let's go ahead, and what I usually choose to do and the reason that I like the kind of passing a string in is that you can kind of just create a union type.

So for us, we're going to say we've got, not dangerous, let's go with destructive, I think what I promise to do, cool? And so we've got this type, you don't have to export the type. I choose to export the type so that if I wanna have higher order components later on that kind of then take some of the same props as a button, I have access to that type.

That's the decision you can make, you could not make as well. And so then we actually say that it's ButtonProps, and this is gonna break, as you can see, but there's children. And when we think about the lowest level of our design system, right? We're basically creating stylistic wrappers and sometimes convenience wrappers over basic HTML components, right?

And what we don't wanna have to do is re implement HTML and all of the specs. What I don't wanna have to do is, I don't wanna have to say className is a string. And one can look at the MDN docs to see how many things you would have to do particularly with input fields, all right?

Sometimes you want to actually say maybe you can't pass checked, because they're all like, you can take any prop you want to in an input field. You might choose to get a little bit more nuanced, but as the kind of lowest level of our design system when we go to implement it, we wanna say like yes, this is a superset of a button.

This is a button that takes all the normal HTML props that a button takes, right? ID, className, what have you ARIA, labels, so on and so forth, right? And then also these additional things that I use as well. And every framework has some ways of doing this. One of the reasons I chose react for this workshop is that, like reacts is actually for all the smack that I can talk about react on a given day has a pretty decent way of doing this.

Where we can say component type, or component props rather, no I think it's component type. Nop, it's component props, that good old IntelliSense works. And so you can pass it just the name of the HTML tag. So this will actually reference all of the HTML props that a button takes.

So we're saying everything that a button takes, right. And also this new idea of a variant. And that's really, like I said, you can use appearance, you can use variant, you can use kind. The only thing that you've got to think about is, what's now gonna conflict with what's already in HTML, right?

So, like, ID is a bad idea, [LAUGH] but generally speaking, and then also you gotta think about reserved words. And stuff like type can get a little dicey as well. So now you can see that we don't have an error anymore. And this now takes everything that a button takes plus this new special one.

And generally speaking JSX, like React, Svelte, Ember types are, like HTML will take literally any attribute and not complain. So variant is currently getting passed into this component, and we don't have an error because it's fine. We will kind of intercept that in a second. So yes, we gonna take a look just to save us from writing too much CSS, I started us out with some basic CSS that we can use here as well.

You can grab a version of the CSS as well from the course webpage. So let's go grab that and pass it in. Undoing the design system add one little thing. So here's our CSS and we'll kind of paste it in. Again, lots of little things in here as well to make it all nice and match along with that.

But what I want to draw your attention to is we've got some of the basics of a button in this case, that background colour so on and so forth. And then in here, we also have the idea of the secondary button which is just a few little overrides, colors along with the destructive colors.

And if I wanted to have the ghost one as well, that is all here that we can use. Neat, so we're gonna grab that. We'll paste it in here. And we've got this in place, and now we want to pull this in. Now, one of the things I'm going to deal with real quick is that past Steve and current Steve have a different philosophy on this.

And so I've got the regular button I'm calling this button-secondary. I'm actually gonna keep them as just secondary and destructive. I'm using CSS modules, so I know that they're scoped. If this was just in a global CSS file, you would want more whether or not it's full on BemO or CSS whatever.

There's probably lots of secondary things, lots of disabled things, lots of dangerous things in your application, but I don't have to deal with that in this CSS module here. So we've got the CSS and now what we can do is, and this is like, you get this for free in Vite, I think you get it for free in Webpack too.

Most modern things have some way of importing CSS. So I'm going to import styles and yeah, under the hood Vite or Webpack or what have you is actually turning into a JavaScript object and it's hashing the classes and all that stuff. But that's a different story for a different day, what we have are these styles.

So now I can pass in className, and I can say styles.button, right? And I should see, look, my button started looking like a button, right? Like we're doing the thing, and we've got the secondary button, and it doesn't look like a secondary button. So we've got to deal with that in a moment, right?

But we have the concept of some button that looks like a button. We're killing it. And so we've got that in place, now we need to think about the variants, right? And we know that's a prop that's being passed in. So we can actually say, what I'll do a lot of times in this case, is I'll do a little bit destructuring here.

Again, mostly framework agnostic, it might be args and another one where we'll take all the props, but I want my variant and IntelliSense helps us out here. So now the props is going to be everything, but variant, we've got that in place. So what I can do is I can start like we'll refactor this in a second.

I'll show you some really useful utility libraries that will make this a lot easier for you. But let's just do it the kind of most straightforward way because yeah, it's one of those things which is like you might not need it. So we'll say that let className equal that styles.button.

And I have one screen right now because I'm plugged in here. But what I will typically do is I will have the main screen as my editor or even a larger 27 inch screen. I will have storybook right next to this and like hot module reloading is a thing.

And you don't have to worry about the entire page or scrolling or anything along those lines. So, the important thing that you should see here is nothing has changed. However, we go into controls, this is pretty cool. Not only did it add the control for variant, which is not mentioned in any of my stories yet.

It will be, but it's not mentioned in any of my stories. But it knew my three variants, right? And this is like what I was talking about before. One of the reasons why, even if you don't use TypeScript in your larger Codebase, you might consider it for the design system portion of the show.

That said, if one wanted to write JS Doc, which I do a lot these days because I realized that I don't always want to compile Typescript to JavaScript, particularly for scripts and stuff like that that run in CICD and stuff along those lines. You can also write JS Doc, it's just a little bit more verbose.

Both will work. The nice part about writing JS Doc is you can run it like a JavaScript file on node and you can still have TypeScript check it, which is super cool. But we'll just use TypeScript, but the fact of the matter is that React Doc Gen went through the file and was able to figure out roughly what the options were and automatically populate the control with that.

Now, can it do it for all the things? It cannot, and sometimes we'll learn how to give it hints later, but we've got the basis in here. So then we want to say, well, if the variant is secondary. Again, Tele Sense is my friend here. We'll say that the className, and we'll just append a space and say style-secondary.

You can make an array and join them all at the end. Whatever makes you happy is I'm going to replace this with library in a hot minute. So we'll say variant destructive, then add the className of destructive, right? So now, if one was developing, is it primary, is it secondary, we can see that we have all the variants of the button in this case.

So I'm gonna do the first one and I'm gonna cut you loose for a second and I will make a branch and push it up so you can follow along. But I am gonna do the first one here where I actually set the variant in this case now, mine has kind of set that if you don't say a variant, like the initial styles are the primary button.

But let's go take a look at what that looks like in our storybook now. So I could say something like args. I can say that the variant here is primary. And I can go down here, and say that the variant here is secondary and get all that in place.

And now if we go to the stories, we can see that we've got the primary button, we've got the secondary button. And if you pay attention to the controls on the right pane, you can see that it has like filled in the stuff for you. You can always go ahead and change this and see it all live and in real time.

This is for me, I'm gonna make a hard pitch on why this is important for testing accessibility audits and all those things, but I just want to sell you on the initial fact that as a development experience, taking each and every little component, even if you don't quote unquote have a design system, you're just building components, right?

Things that are not even reusable, we will break out into various things as well. And we'll see with all the various tools up in here and a hot minute, we'll take a look. But what I would like to have you do right now, is just go ahead and add the destructive story.

>> What's your thought process regarding I will create a separate story versus, I will let the user use the controls.
>> Yes.
>> If we have five props with six values for each prop, it becomes 30 stories.
>> Yeah. So yeah, the question is, at what point do we have one story and we click around and stuff along those lines, and it will point do we have a ton of stuff Stories.

And so one of the heuristics that I use in my thought process around that is later on, we're going to see the ability to write like, effectively, like integration tests, right? Like, we wanna write a test that then types into this field. Well, by definition, that's got to be a story.

Visual regression tests, that's got to be its own story. So it's like, do I want to test this thing, right? Is my like heuristic for whether or not I write a story. And I have found that on one hand in the beginning, I was like somewhat conservative about the number of stories that I wrote.

And I did realize the stories arefree, they don't really cost me anything. Yeah, there's some like visual clutter there. But the main heuristic that I use is, because like actually, if I wanted to see a secondary button go in here and clicking on it every time and yes, it is stored in the URL.

And you could theoretically like if I refresh, it was all staying there while I'm developing. But like, if I want to have a visual regression test, or if I wanna write an accessibility audit test. If I want to test it in any way in automatic fashion, then I want it to be its own story, right?

I might not like for some of the little tiny little things like will I write one for when you've got the idea of like a button with an icon, I'll probably have story that is button with icon will I write a story? If I have a whole bunch of stories to the icons, I got the buttons, right?

We'll see this later on like, we're going to do the various of button than we do sizes, right? Already if you take primary, secondary destructive small, medium large up to nine, you add in the ghost, you're up to 12, right? So on and so forth, right? You add an icons and then you double all that, you up to 24.

What I will normally do is I'll probably have a story for my primary, my secondary and my destructive button. Maybe one for those in the disabled state, because I might not see that you can't click on it or something along those lines. And then I'll have a story for a small, medium, and large button, but while I do destructive and large?

No, because generally speaking, I feel like I know how that's going to play out. If I have one for a destructive button and I have one for a large button. I don't until I get burned by it one time, then I add a story. It's the same way you add a unit test when you find a bug or something along those lines.

So I kind of like, do I need to like run a, I got on a May test is my heuristic, right? And then did I deal with a bug around this, is my other one.

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