In this video, we will be building a reusable,
extensible, and production-ready UI component, similar to Shadcn/ui. We're going to be working
with Tailwind, Class Variance Authority, and CLSX. And also, by the way, this is exactly what I've
done for all of the UI components in my upcoming React course, Project React. If you're interested,
it's the first link in the description. So over here, I have a project that is created and
running with Vite, and we're going to be creating a component inside of the components
slash UI folder over here. This folder is meant to hold any React component that is pure UI,
right? That means a simple component that usually doesn't have any state, doesn't do any complex
logic, and only renders simple UI, for example, a button component or an input component. For
this video, we're going to be building a button component that is very similar to Shadcn/ui. In
fact, it's actually going to be a simpler version of their button component. This will then allow
you to build your own components just like this in your project. Or if you want, you can go and use
the ones from Shadcn/ui and be able to easily edit and understand them after watching this video.
You're going to find a link to this repository that is going to contain all of the code that
I show in the video in the description. This is also going to have all of the dependencies with
their correct versions, the ones that I used for this video. So first, let's start by creating a
new file inside of components slash UI. So we'll come here, we'll right click new file, and we're
going to do button.tsx. Then we can close this for now to make a little bit more space. And inside of
here, we can create a new React component. So we can do our GSFC. This is a snippet that I have,
if you're curious, I have a video on my VS code setup that you can go and watch. This component is
going to be called button like this. And for now, it's not going to return anything, then
we're going to come here at the top, make some space. And now we're going to start
defining the props of this component because we are working in TypeScript. So I'm going to do type
button props. And this will be a type in here, we actually want to give this button our own
custom button component, we want to give it all of the props, all of the attributes that you could
give to otherwise a normal HTML button element. And in React, to do this, we can use react dot
HTML attributes. And then here we can pass HTML button element like this, this is going to make
these button props here be exactly everything as the HTML button element props that we could
pass if we were to just use a normal button. So then we can come here to the button, we can do
props, and then we can type these as button props just like this. And then we can come here and just
return a button like this, and then pass the props over here. So what we're doing here is we're
taking all of these props from this component, and we're just spreading them to the HTML button,
which means that whatever we pass here, when we're actually using the button component, will also
go to this HTML button. That's it, we're just using the normal HTML button, but we're wrapping
it in our own custom button. And with this, we now have a fully working button custom component that
we can use anywhere in our app. So that means we can come here, we can find our app dot TSX file,
we can open this. And we can replace all of this here, which has calls and solutions, which I put
here as a reminder to say that, hey, did you know that over 50% of you watching this video are not
subscribed, which honestly is unfortunate, because you're getting a ton of value here. And I really
think that that deserves a subscribe button. So if you haven't subscribed, the button is right there,
and it's free. So now that we are subscribed, hopefully, we can remove all of this and we can do
button and then import our button from components UI slash button. And then we can call it like
this. And we can pass it some text inside. For example, we can do click me like this. If we now
open up our browser, we can see our button here on the screen, if I can make it a little bit
bigger so that you can see this is our button, this works, our cursor changes to a pointer when
we hover over it, which is the default behavior of a button. This works exactly as we expect.
Now the next step for us to do is, of course, to add some styles, because currently, this button
looks a little bit boring. Now to add styles to this button component, we're going to be using the
class variants authority library, this will allow us to configure different variants for a button,
each with its own style. This will make our button reusable and have different styles easily based
on a single prop. The first thing that we want to do is we want to come here to button props. And we
want to add another prop that is not part of these props here, the HTML attributes of a button,
we want to add something that doesn't belong in there. What we're going to do is we're going
to do the variant like this. And this is going to be the different variants that our button can
have. And we're free to configure this in any way that we want. For this video, we're going to keep
things simple, we're just going to do two options, it's going to be primary, or it's going to
be secondary like this. So our button can only be of type primary, a variant primary, or a
variant secondary, then we're going to come here below the button component. And we're going to
define something new, we're going to do const, and then button variants. And that is going to be
equal to a function, which we're going to do CVA. And that is going to come from class variants
authority. This is a function that returns a function. So this button variant, it's highlighted
here as a function, this function will return a string that we can use in the class name property
of this button HTML element. This CVA function over here comes from class variants authority.
And it essentially allows us to set some default styles for all of our buttons, regardless of their
variants, and then apply some variant specific styles to our button. So if our button is a
variant primary, we're going to apply some styles. If our button is a variant secondary, we're going
to apply other styles. And we can override the default styles based on the variant that we pass
as a prop. So first, let's start by adding the default styles. These are the styles that will
apply to any button regardless of its variant. So I'm going to pass here as the first argument
a string, these will be the default styles. And we're first going to add some padding to the
button. So we're going to do padding y of two, and then padding x of four, then we're going to make
this button a little bit rounded. So we can do rounded MD, then we're going to want to add font,
semi bold to make the text inside of the button a little bit thicker. And finally, we're going to
add a hover state. So we're going to do a hover. And then if the button is currently being hovered
over, we're going to do opacity. And then 15. Just like this, this is simple enough, but it's
going to get the job done for what we want in this video. Then as the second argument to this
function, we can pass our variant specific styles. So we're going to pass here an object. And then
we're going to do variants, like so variants. And this is going to be all of the different variants
that we can configure for this specific button, right. So this variance over here comes from CVA,
you cannot define how this key is named over here. But this is going to be an object. And inside of
here, you can do anything that you want. In our case, because we have a variant property here that
can be either primary or secondary, we're going to put variant over here. So very end like this.
And this is going to be an object as well. And now we're going to give both of our keys primary and
secondary. So we can do primary, that is going to be an empty string for now. And then secondary is
going to also be an empty string like this. With this, we've now passed an object as our second
argument to CVA, this object has the variants key, which comes from CVA, you cannot control this.
But then we provided a variant property here, which we can use to determine if it's secondary,
put these styles, if it's if it's primary, sorry, put these styles, if it's secondary, put these
styles. And this you're going to see when we actually use this button variants over here,
because this is a function, and we're going to be able to pass this variant prop from the props
of this component to this function. And that is going to select the appropriate styles and merge
them with these styles over here, and then style our button correctly based on the variant. So
let's now start defining our styles. So we're going to come here and in primary, we're going to
do background gradient to right, so a gradient to the right from and we're going to do primary 500.
This is a color that I have set up in my tailwind config, then to primary 700. This is also another
color that I set up in my config. And these are the basic styles of my traditional button of
the color palette of cause and solutions. And then we're also going to do text black, because
the text should be black on a background color like this. Then in secondary, we're going to keep
things a little bit simple, we're just going to do background grayscale, and then 700. And we're
going to do text white, because in this case, the text should be white, then the next thing
that we want to do is actually add a default variant in case one isn't provided. Because the
thing is, we don't want to have to provide this variant property every single time. Actually, we
should make this optional. Because if this isn't provided, we should just assume that the button
is going to be a primary variant, because that's most likely what you're going to want in your
project. So this makes sense. And then with this, we can come here and at the same level as this
top level variants over here, right below it, we can define default variants and pass this as an
object. And then we can do variant like this. And this is now properly typed, as you can see here,
because we have defined variant over here, that's where this is coming from, I can set for variant,
I can do primary, it knows exactly the values that I have available, because again, they are coming
from this object over here, we define variant, and then we define primary and secondary. So these
are valid options that we can use. And with this, now we can actually use this button variants
over here in the class name prop of this button, because everything else is configured. So
we can come here and we can do class name, name like this. And we can pass it here, we can do
button variants, this is a function that we call, so we're going to call this function. And then we
have to pass over here, we have to pass a variant, which can either be primary or secondary. So this
we're going to have to access it from the props of this component. So what we're going to do here is
we're going to destructure this. And we're going to do variant like this. And then the rest of the
props, we're just going to do dot dot dot props like this, because we don't want to destructure
every single prop, we just want to goAnd lastly, we have a button variant. So, we have the style,
and we have the style. We can group them in this props object here, and still spread them exactly
like we had it before. So, then, now that we have this variant here, we can come here and pass it to
button variants. We can do variants like this. And now, the variant that we pass from the props of
this component is going to get sent to the button variant, which is then going to configure
the specific styles of that variant. Again, if it's primary, it's going to take these styles,
and it's going to merge them with these styles, it's only going to take the styles that we
have here. That's how this works. And now, if we go back to our browser and open up the
application, we can see that our button now has the updated styles. These are the primary
styles of the primary variant. That's the gradient background that we configured here. And as you
can see, in our app component, this is how the button is used. We haven't provided a variant, so
it means that it's being defaulted to the primary variant, which actually means that we can come
back here to app, and if we wanted to set this on a different variant for this specific page, all
that we have to do is come here and do variant, and then we can do secondary like this. And now,
just with this, we have our button over here that completely changes styles by a single prop. Now,
the cool thing about this is that the button is still rounded. The button is still reducing
opacity when we hover over it. Everything else that we configured to apply to all buttons
is still being applied. The only thing that is different is the styles specifically to a variant
that we've configured in this object over here in this one here in this primary and secondary
object. Only the styles that we configured here are specific to a variant. Everything else applies
regardless of the variant. And one thing to note, I did mention this, but it's worth rementioning
here. We could have called this any way that we want. This could have been size. This could have
been any other property. We could have passed this any other values. All that we would have to do is
just configure those same values over here, and it would work exactly as we expect exactly in the
same way. We would just have to pass that property here and everything would work right. So you're
completely free to customize this in any way that you want. And oftentimes, if you're working in a
real world project, you're going to have a design system that you have to adhere to. And you're just
going to want to implement that design system in these props of this component. And it's going to
work and it's going to be super, super efficient. Now, there's one more thing that we want to
do here to improve this component a little bit better. We also want to make this component
be extensible so that if this app component here wants to apply some custom styles that does not
belong to any variant that are perhaps just one of styles for this one specific page or component, we
want to allow this component to the app components to configure that on the button and overwrite
any specific styles that we set in this button variants object. And so the way that we're going
to do that is we're also going to come here and we're going to access the class name property of
the button props that again comes directly from these props over here. Every button element in
React will have access to a class name that we can destructure over here. And then what we're going
to want to do is we're going to also want to take into account this class name property along with
the button variants and merge these two together and use the class name to overwrite anything in
button variants. And the way that we're going to do that is actually super simple. We're going to
have to create our own utility function that we're going to use to merge these two things together.
So what I'm going to do, I'm going to come in here inside of the utils folder, and I'm going to
create a new file and I'm going to call this cn.ts. I'm going to close this and here I'm going
to define a function, export function, cn.ts, I mean not .ts, the file is .ts, but the function is
called cn. Cn is short for class name, that's why this is called it this way. This function is going
to use two things. It's going to use the tailwind merge function, twmerge from tailwind merge, which
is going to safely merge all of our classes here. Because in tailwind, the order of the classes
matter and you want to be able to safely merge them because if you don't, you might have some
unexpected results and some styles that you think are being applied are actually not being applied
because they're not implied in the order that you expect. The second thing that we're going to have
to use is a thing called clsx. This is a library that allows you to pass multiple things and
convert all of those to a string that ultimately results in the class name of a specific element.
We're going to go over this as we're using these, so don't worry, this is going to make sense. So
first, we're going to define some arguments to the cn function over here. So we're going to do dot
dot dot and then inputs like this. These are going to be all of the potential class name inputs that
this function can receive, and we can have any arbitrary amount of them, we don't care how many
there are. These inputs are directly going to be sent to clsx, so we don't even have to manage them
manually ourselves. So what we're going to do is we're going to type these and this is going to be
class value from clsx, as you can see over here. We're going to make this an array of class value.
If we go here to the definition of class value, this can either be a class array, so an array
of classes of strings, or a class dictionary. So this is here a record with the classes as keys
and then any value as the value of the class. Then it can also be a string, a number, null, boolean,
undefined, anything that we want. This is clsx, and it's going to use this to actually return
everything that you pass into one single string that we can use as the class name property. So
then we're going to take this string, we're going to take all of these inputs here, we're going to
return, and we're going to do twmerge. This is the function from Tailwind Merge, which is just
going to merge a string of classes, of Tailwind utility classes, and make sure that there's no
duplicates and make sure that we can safely use that on the class name property. And then we're
just going to pass this, we're going to do clsx, we're going to import this from clsx, and we're
going to pass the inputs that we received over here. So we're not directly doing anything with
the inputs, we're just passing the inputs to clsx, and then we're passing the result of that to
Tailwind Merge and returning that as the return value of this function over here. Now, if you're
a little bit confused as to how this all works, that's totally fine, I understand, you can go and
look at the documentation of clsx, see how that works, and then look at Tailwind Merge and see
how that works as well. It's pretty simple. And if you just look at a little bit, you'll get it,
don't worry about it. And then now that we have this function here, all that we have to do is come
back here to our button and just use it. So we can actually remove all of this button variants here.
And actually, let me just make sure that I copied this correctly, we can just remove this and then
wrap everything in CN. So we can do CN and then import this from utils slash CN. And then we're
going to call this and we're going to put here the first input, this button variant, which is
going to be a string. And as the second input, we're going to pass class name, like so. These
are the actual inputs to the CN function that we defined here. So button variants with this is
going to be a string that is going to be the first input. Class name is going to be a string that
is going to be the second input. This is going to come here and it's going to resolve and merge
this with Tailwind Merge. And that is going to return to us one single class name string that
we're passing to the button class name. So now if we open up the application, the button looks
exactly as we had it before. But now we can come here and we can go to app and we can provide any
class name that we want. So we can do class name and we can do, for example, background red 500 and
set a background color that is going to override the background color of the variant of type
secondary. Now, if we open up the application, our button has a background color of red, which
is exactly what we specified. And this is a great way to build really extensible and really
composable components that you can use across your entire application. And this is exactly what
Shadcn does with all of their components. This is literally the same thing that we did here. The
only difference is that in Shadcn, they usually have a lot more classes, and it's a little bit
more overwhelming to read and understand. But this is really simple. And again, it's exactly the
same thing that Shadcn does. And we're using the same libraries in the same way and producing the
same output, just in a simpler way that is easier, perhaps for a beginner to understand and to work
with. So if you're interested to learn more about this and see how Shadcn works, you can go to their
website, UI dot Shadcn dot com and see how they build their components and actually integrate them
in your own applications. And if you want to see more examples of components just like these,
I would strongly recommend that you check out my upcoming React course, Project React. You can
find it at cosden.solutions/project-react, or it's going to be the first link in the description. As
I said in the beginning of this video, all of the UI components that I built in this project in
Project React all followed the same exact thing that we did in this video. So these are a great
example if you're trying to learn React. And this course is going to teach you literally everything
that you need to know to be able to build a real world, big and complex application with React. You
will be guided step by step, every single step of the way with code and videos. And by the end of
it, you'll be able to confidently, meaningfully contribute to any React project that you work on.
So again, cosden.solutions/project-react or click the first link in the description. You will
not regret it. There is no other course like it. I promise you. If you enjoyed this video and
you want to see more videos just like this one, make sure to leave this video a big thumbs up.
You can also click here to subscribe or you can click here to watch a different video of mine
that YouTube seems to think that you're really going to enjoy. And with that being said, my
name has been Darius Cosden. This is Cosden Solutions. Thank you so much for watching and
I will see you in the next video. Ciao, ciao.