React and TypeScript, v3

Component Variants

Steve Kinney
Temporal
React and TypeScript, v3

Lesson Description

The "Component Variants" Lesson is part of the full, React and TypeScript, v3 course featured in this preview video. Here's what you'd learn in this lesson:

Steve discusses class variance authority, explaining how it simplifies creating variants for components across different frameworks. He demonstrates how to define button variants and discusses the utility type called variant props for managing design system supported variants. Steve then walks through the concept of polymorphic components, showcasing how to create flexible components like buttons and containers that can render as different semantic HTML elements based on specified props.

Preview

Transcript from the "Component Variants" Lesson

[00:00:00]
>> Steve Kinney: There are some other super interesting things you can do. Like Scott, you said you're working on a design system, right? One of the things that usually happens is you end up with a bunch of variants, sizes, so on and so forth, right? And so like a button is a great example: primary, maybe secondary, ghost, danger, one that's a link, so on and so forth, right? And you might have different styles set up for that, right?

[00:00:36]
There's a really great library, I guess it's definitely not React specific, but it is useful for design systems in any framework. I've used it in Svelte and React, and I'll kind of show it to you because that's why I put all of these components in there so we can see some of it. So if we go into the button that I've been using, like in this application, if I go into button classes, right? There's this library, I'll show it to you just so we have it in the screenshot.

[00:01:12]
I think it's just CVA style. Yeah, it's a class variance authority, which yes, is a low-key reference from the Marvel Cinematic Universe, which is a way to kind of like create variants for your components very easily. So I pull in this class variance authority, and this is just a JavaScript object. What's really cool about this is like I used to work on a bunch of Svelte apps. I use React again now.

[00:01:43]
None of this is all completely agnostic, right? And so with this, you say CVA, you give it all the classes that buttons get no matter what, right? And then it's tricky because variants is all the different things, but then I have a variant called variant, because I don't know what to name a button. Type feels wrong, variant feels wrong, kind feels wrong. Type also means a thing on the button itself because you can be a type submit or reset.

[00:02:12]
I don't know. And then you gotta make it consistent because you don't want some to be called variant and some to be called—I use variant. So you have a bunch of variants, and so now, obviously if they give it the variant primary, we'll give them all these Tailwind classes. Secondary, we'll give it all these Tailwind classes. These are the ones they all get. This is the one that they, depending on which one they pass in.

[00:02:33]
And this is like fine, this has nothing to do with TypeScript. Yeah, and I can say like the size and the various different classes and default variants, so on and so forth. Where this becomes powerful is there is a utility type, we're just talking about utility types and why they're good, a utility type called variant props, right? And if I give it the type of button variances, right, and so I'll kind of show you, I'll just pull it up, so like, just separately so that we can see it uniquely.

[00:03:17]
If we look at this, it is the actual props like React props for the various different variants that my design system supports. So now I say that the base button props are, hey, whatever variants I support my design system, right? Plus, you know, I'm going to let them pass in some Lucide icons and loading and some other unique stuff, and I could say, you know, this one shows, I have this one is a button, both as a button and button as a link, right?

[00:03:53]
And so, I've got these base button props for the like aesthetics of the button, right? Where I am, whatever all the variants that are defined in code, derive the type, don't make me make a bunch of variants in my code, and then make the same variants like in my types as well. Here you literally in your actual legit code, are defining, okay, here's the sizes of my buttons, here's the primary, secondary, danger and ghosts, this is the classes that get applied.

[00:04:24]
It's going to do all the application classes for you and have sensible defaults, that's why undefined and null are valid. If I took this out, you'd have to pass them in, but I'm like, if you don't pass one in, then there's a primary button of size medium, right? But then it will derive the component props from your design system design tokens, and then whatever else you choose to add, and then I decide in a little bit, are we extending everything that a button can do, or are we extending everything that an anchor can do, right?

[00:05:04]
And if, like, you know, like I use a discriminated union and see it's all coming together, right, to say like, hey, if you have an href, right, you get, you're going to be on this side of this, like the same way type was the one doing it in our reducer earlier. If you were type increment, you couldn't pass the payload value, but if you're type set as a number, you could, right? If I catch an href on you, then I'm going to assume that you have everything that an A tag has.

[00:05:37]
Because the discriminating union will opt you into that side of the discriminating union. If you do not have an href, I'm going to assume you're a button, right? So now I have this button component. If I give it an href prop, it's got the style variances from my design tokens. These icon like loading full width, other properties that are unique to all my buttons. And if I give an href everything else that an A tag has, target, so on and so forth, right?

[00:06:15]
If I do not give it an href, then it has all the properties of a button, right? And now I just use this thing and I don't have to have button and button link, right? The props will decide what goes on in here. And I'll show you another way to do this as a polymorphic component in a second. You know, I get all the classes, there's some fun stuff with the icons, don't worry too much about it. But the more important part is like, cool, was an href in there?

[00:06:42]
Cool, I'm returning an A tag with all this stuff applied. Otherwise, I'm returning a button, right? So my code is like varying on whether or not we have the href, but then the types, if I go to use this, in fact, let's just use it right here real quick. Now I have my button. And if we try out variant. Oh look, it's the ones from my design tokens that were like written in code that will apply certain like Tailwind classes, so definitely make that primary.

[00:07:34]
And then type, which is definitely on a button, or is it, yeah, hold on. Put that in there too. What else does only a button have on it? We got role. For that, I think, right, is a type, yeah. I think you can just like literally, I don't know if that one, it's like some of the, all HTML props are optional, and some of them just don't have really great types because HTML turns out pre-existed types.

[00:08:00]
Trying to think, what is something only a button has that a link doesn't have, if only I could like look at the types and find out. I'm not going to because that's really boring for everyone involved, but if one needed to, one could go into the button attributes. Yeah, I don't think that a—yeah, like form is optional, disabled, yeah, that should do the trick. Disabled should not be able to exist on a link, right?

[00:08:30]
Cool. So this like, it's a button because I didn't give it an href. The, and if I tried to give it like target, the problem is everything is technically optional on a, like they're pretty loose in the real HTML stuff, but if I give it an href of example.com, then ideally, yeah, target should now be a thing. And I get it like the moment it got an href, my type system changed for this component completely, right?

[00:09:09]
And it now knows that in this case, it takes a target and it knows how to fill that all in. If I took away this href, like, but yeah, like, you'll notice it doesn't give me the autocomplete because it has none of, like, HTML tags will take anything you give them, but like you'll notice it doesn't assume anything about it being a tag, right? So I get to have a lot of flexibility and all it really is, is those discriminating unions that we saw earlier, you know, this is obviously the one that I really use, so it's got like all sorts of crazy stuff in it, but like a simple version exists where it is this discriminated union, right?

[00:09:46]
It is using these utility types from React. It is, you know, the base one is using the variant props utility type from class variance authority, right? You can begin to like stack these abstractions, and then you're not hard coding a bunch of stuff. You're not saying like anything all the time. You can kind of get some really interesting things. The other pattern in polymorphic components that I think is really powerful is, I think I have it in my card component, is this ability to say, like stuff like card or container, these like very generic components, like I used to work at Twilio and they had a design system team, and they had this component called box.

[00:10:41]
Really, everything was a box at the end of the day, that was the like statement. But like, I probably want to write semantic HTML, right? I don't want everything to be a div all the time, right? So here we have a prop called A, which I am renaming to component with a capital C so that JSX leaves me alone, right? Because like components have to have capital letters, right? So it's really when I use it, it's going to be as, and it defaults to article in this case, right?

[00:11:20]
However, like, if I did something like, you could actually, you probably saw it in other parts of the codebase, like when we were doing stuff before, I just didn't pay any attention to it. I could say card, and I can say as. I can say as a div, right, or as an article, or at one point I think I have it as in the, in the Grand plans app, I think you'll see it as a list item, right? And that will then render it as any of those semantic HTML components, right?

[00:11:57]
And I get all of the like fun stuff from that in this case as well, where is it? Poo poo poo poo. Scroll up a little more. Yeah, so it's really a string of some HTML tag, this element type. So it's got to be something in there when I like. You can see it autocompleted, and that's what gets filled in there. So if I say as article, that's article. If I say div, that will be div, right? And so you can kind of create very flexible components and be able to kind of build everything out kind of like that.

[00:12:27]
So the stuff like the class variance authority for the design tokens, again, if you're using that, if you own the design system, right, great, but it will begin to give you the ability to have your code be the source of truth for your types. The same way we saw with Zod, right? You can define a schema and then infer the types from that schema, right? You can kind of take your types and kind of make it a source of truth for your code, but you can also take your code and use that to inform your types so that if you make any of these boo boos, you can see it anywhere.

[00:12:58]
And like some of these will be as you need it and like they're not always generally applicable everywhere, but they work pretty well. Questions, comments, concerns, cries of outrage. You know, like, I need to think about that for a little bit. Like this, like the goal of this section is, I want in 10 days, you should be walking down the street, forgetting that you listened to this conversation, and go, oh wait, that problem.

[00:13:23]
I could do that thing, right? Like, then it will at one point, I don't know when in the next six months I will have saved you a bunch of tedious work, but sometime, sometime, it might be nine months from now, you'll be like, oh yes, I can do that thing, right? So I'm just trying to plant that in your brain at this point as well. And all these components, if you want to like look at them, I included them in the same repo as everything else for your own enjoyment as well to kind of like poke around in there.

[00:00:00]
And this is the one I literally like ripped out of my own codebase and dropped in there. So, I'm not saying it's perfect, by the way, because I ripped it out of my own codebase and dropped it in there. I'm just saying like, that I ripped out of my own codebase and put it in there.

Learn Straight from the Experts Who Shape the Modern Web

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