Main

Build a Reusable Component in React (Shadcn/ui, Tailwind)

🚀 Project React → https://cosden.solutions/project-react Join The Discord! → https://discord.cosdensolutions.io VSCode Theme | Font → Material Theme Darker | Menlo, Monaco "monospace" In this video we will be building a component in React that is similar to shadcn/ui. We will be using tailwind, cva (class-variance-authority), and clsx for this. The component will be reusable, customizable, and will be a simpler version of what shadcn/ui offers. Through this video you will learn how to build production ready UI components that you can use in your projects. You'll also be able to easily understand shadcn/ui's components and integrate them in your own project!

Cosden Solutions

2 weeks ago

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 i
nside 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 p
ass 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, whic
h 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 curr
ently, 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 attrib
utes 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  va
riant 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 bu
tton 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'r
e 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 s
tyles, 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 k
eep  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 th
at 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 g
oAnd 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 pro
vided 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 r
educing  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 worl
d 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 t
hat 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 cla
ss 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 goi
ng 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 se
nt 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 co
mponents 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 t
o 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 bei
ng 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.

Comments

@saas-design

Great video with a ton of free value on React. And being in the design space, I love Tailwind. I can tell you'll be at 100k subscribers soon, the helpful content and production is on point🚀👏

@Farruh_13

Please make a video demonstrating how to animate components on mounting and unmounting without any animation library, like shadcn ui does. Shadcn ui animates components with tailwindcss-animate plugin, by using data attributes.

@medas4923

Instead of copying the buttonVariant type to the props, you could infer it with the help of VariantProps like this: VariantProps<typeof buttonVariants> and you won't need to touch props ever again, even if you later decide to add an additional variant or a new class.

@51Grimz

What a quality video! It is great to show how to make reusable components built on top of base html elements. I like the little things like the smooth mouse movement.

@mohammadkhosrotabar2215

I really needed this, thank you so much!

@demicoderr

I'm really anticipating Project React! I really pray it is affordable.

@samahgad241

Easy to follow, thanks aalot Cosden❤

@eca8799

amazing explanation thank you so much

@sahibibadov

react-twc + tailwind-variants the best.

@TheIlkzz

Is there a way to do the merge with other component/css libraries? I like this setup but we don’t use tailwind at work for example. We use bootstrap so would be awesome to do something similar

@nemeziz_prime

This amazing 🔥 Could you also do a video where you show how one can make their custom production ready UI component library using Storybook, Tailwind and say Shad CN UI?

@nurpaisorozulbaev7451

Great! Thank you! Do you have any typescript tutorials?

@kaswanpradeep

Can you do one for input element, as it has different types, it would be really awesome if you do it.

@user-dc7jb3vk5y

There is no need to wright ButtonProps, but only Props, because we understand that it is Button component

@antsii

Should've used forwardRef to be able to pass ref?

@hithere7145

wht your take on nvidia's ceo statement

@heysahilsingh

I was expecting something different, as this component is too easy to create. I think a dialogue box, a card or something would be better. But still, m gonna like this ❤️

@sae4622

What is difference of using HTMLAttributes and ComponentProps?

@somtechnology1811

can you make a full project using React and taliwaindcss

@d1zzy155

Whats difference between HTMLAttributes<HTMLButtonElement> and ComponentProps<'button'>