shadcn is extremely lightweight and primarily built on top of Radix. After initializing, you just copy over the UI component that you need, and use TailwindCSS for all of the styling.
The example blog that we're building is a simple app. It's a single page that uses a two column layout. On the left side we have a server component that holds the blog entries in memory and displays them. On the right side we have a client component where we build out a form using shadcn UI components.
Transcript
Let's start off by taking a look at the ShadCN component library. The ShadCN component library is loosely, I'd say, affiliated with Next. I think the guy that actually is the primary contributor on ShadCN works at Vercel. I don't think it's in any other way coupled to it. The unique thing about ShadCN is how it's installed and how it's updated.
When you create a Next.js application, you can use this ShadCN init command, and what that does is actually copy in code into your application. Then As you add button and input and any other of the components that ShazCN has, those are copied in to your application. So it's kind of a little controversial in that way, but there are some advantages. So one thing is that you can go and modify the theme, the way it does, for example, a button simply by editing the code that it copied into your application. Now, if you're thinking, oh, wow, it's going to copy a lot of code in my application, it's really not because ShazCN actually sits on top of Radix primarily.
It sits on a couple other headless libraries as well, but primarily Radix. So the button wrapper is very light. The select is very light. Radix is a headless UI. You basically just give it the classes, you handle the styling, and it handles all the rest.
So really, it's a pretty light wrapper on top of Redix. All right, let's go take a look at how to implement our blog using ShadCN. So I'm going to start off by creating a next app called ShadCN test. The important part here is I'm going to use Tailwind CSS. ShadCN relies on Tailwind.
So you want to say yes here for Tailwind CSS. Other than that, everything else is up to you. Well, except of course the app writer, you want to use the app writer. All right, let's bring that up in VS Code. All right, now that we got our ShadCN up in VS Code, let's go and initialize ShadCN by running the ShadCN init command.
To do that, I use mpx. Mpx is one way to run commands from packages. In this case, I'm going to use the init command from ShadCN at the latest version. So that's ShadCN at latest, followed by init. Init initializes the repo to add ShadCN.
We use the default look. We'll use CSS variables for colors. And that's it. Now we are ready to use ShadCN. So I'm gonna get the layout set up first.
Let's go over to our source app layout. And in there, I'm just personally going to set us into dark mode. That's just as easy as specifying that we want dark as our class for our body. And I'm gonna add a little padding just to bring it in off the side, so that's P5. Now, before we get the layout going, I gotta get the infrastructure of the app going.
So let's go over here to our source directory, and I'm going to bring in the support for the blog infrastructure, and we'll walk through that. So over in the instructions, there are two files. The exported type from types is simply just blog entry, and a blog entry is an object that has a title and some text. The title of the blog entry and then the text. And then we have the blog file.
Both of these are going to be in the source directory so it's at slash blog and at slash types and the blog system is in memory. So we're gonna have this const entries. It's gonna have our list of blog entries. We can add new blog entries to it. And then we have two async functions, getEntries and addEntry.
GetEntries just simply returns the list of entries and then adding an entry just pushes the new entry onto the in-memory list and revalidates the path. Now, why are these async? They clearly don't need to be async because there's nothing async going on in them. Well, they're async because these are server actions. This is in a file that has use server at the top.
That means that both get entries and add entry are server actions. All right, let's hit save. Now that we have those two files down, now we can actually start building out the left-hand panel that lists our blog entries. Now to do that, I'm going to create a new component in app, we'll call it blog, and it's going to be a React server component in this case because it doesn't have useClient at the top of the file. It's gonna take entries, which is a read-only list of entries.
And then we're gonna iterate through those entries and create H1s for every title and divs for every entry text. And we're just going to use out of the box Tailwind styling for this. That's the really nice thing about ShadCN. When it comes to your typography and your margins and your padding and your layout, you're just going to use Tailwind for that. It's only components like input and button that actually use any kind of external stuff, like in this case, the Radix UI to create interactive components that are also accessible.
All right, now that we got that, let's go over to our main page and I'm going to pair this down to just the home component and I'm going to bring in that blog that we just created. I'm also going to bring in the get entries function from at blog that we just created. That get entries gives us a list of blog entries. Then we'll get the entries and thanks Copilot for the hinting there, but I do want to put a little bit more around this. I'm going to give us a flex box as the primary layout mechanism by default.
Flex is a flex row. So I'm going to give a half column to the entries, then I'm going to give a half column to nothing at this point. We're going to add the form in just a second, but that's what's going to go in the other column. Now, let's go and do our PMPM dev and take a look and see what this looks like. All right, that looks pretty good.
Okay. So now in order to do the forms, now we actually need to bring in some ShadCN UI components. So do that. We again use the ShadCN package, but this time we use the adverb, and then we give it button, input, and form. Those are the three components that we need for our application.
Now, You can see that it's installing each one of those. Actually installed an extra one label because form depends on label. And if you want to see where those go, well, those go in the components UI directory. And let's take something like label. You can see that this is a fairly short file.
We get the React label from RADX UI, and then we're just skinning that with our own classes, and that's under the label variant. So if you wanna change label, you just change it right here, and that becomes your new label. All right, now let's go create our blog form. So we'll create another file, blog form, and we'll make it a client component, because it is going to be interactive. So create our default blog form component.
Now we'll start off by getting the connection to the back end. That's add entry from blog that's that server action for adding an entry. We're just going to use a server action to post the new blog entry to the blog. We're gonna spec out what our form fields look like for our form system. Again, that's just the title and the text, just like we had with the blog entry.
And then we'll bring in our components from ShadCN. We also need to bring in React Hook Form because React Hook Form in this case is going to be what we use to manage the form. This is not in every case. A lot of the component libraries have their own forms management system and when they do we'll use that one instead. In this case, we want to use React Hook form.
So let's bring in that. Now, of course, you could use Formic or any other kind of forms management system. I just find that in my experience, a lot of folks are really happy with React Hook form. So speaking of React Hook form, let's go and use React Hook form to create our form. So we use that form fields type to say what the form fields are, and we give it some default values, and then we use the use form hook from React Hook form.
Now we'll create a non-submit handler. This is gonna get called when we submit the form to the server. So it's gonna call our add entry server action with the data that we get. The data that we get from React-Hook form is just the form fields. And then when we've done our submission, we reset the form.
This is exactly the same submit handle you'll see in pretty much every example. All right, now let's get into actually building out our form. To do that, we use the form component from Radix, and then that wraps a regular HTML form. We give that HTML form our submit handler. So we use form.handleSubmit.
That's actually what's gonna do the form validation. And then if the validation passes, it'll call our onSubmit, which will then submit to the server. We're also going to give it some nice tailwind layout, in this case a flex column with a little gap between each one, so we get some nice spacing between the inputs. Now the easiest piece of UI to add is the submit button, so let's go ahead and add that. We're going to use the off the shelf button and we'll just put a type on it.
So now let's actually just bring that in and see what it looks like right now. All right, we'll bring in blog form, what we put up there, handle my OCD, and then we will put it in here. All right, let's go and run this thing. And there we go. Now I've got our big old submit button there.
So everything looks good, but we actually need some fields to put our blog entry into. So let's go and add those. All right, before we start, our submit button actually has to be inside of our form. That would help. And now let's talk about React Hook Form for a second.
React Hook Form is agnostic to any type of component library or CSS system that you might be dealing with. So what it's got is it's got a way to generically connect a rendering component in this case like input to a field and that mechanism is a controller. So we're gonna get a controller. So we're gonna use the controller component up here from React Hook Form. And the controller takes the name of the field that you want to manage, in this case title, takes the control function from Form, and then you give it a render function.
And the render function gives you everything you need to pass on to, in this case, an input. So inside here, we're going to render out all the ShadCN stuff. ShadCN defines a form item. That form item has a form label, a form control, and then it's got the generic input from components UI input. And then we take that field we got from render and then we splatted in to our input.
I also save. And now we can see that we got our title And that looks great. Okay, now let's do the same thing for our text. So to do that, I'm just gonna copy this and paste it. I'm gonna change this to text.
For humans, we call this content. Otherwise, there we go. Let's go take a look. So we got our title, we got our content. I'm gonna drop something in here.
Some title, some content. Hit submit and there you go. And that's it. Now you understand how ShadCN works for basic primitives like inputs and buttons, how it does its layout. In this case, you're just going to use basic Tailwind for its layout, how it does dark mode, you're just going to use the basic Tailwind for dark mode.
It's just a nice system that integrates really well with Tailwind. So if you're a Tailwind fan, if your applications are on Tailwind, I would definitely recommend looking at ShadCN if you want really good accessible UI components on top of Tailwind. And really what you're looking at is Radek's UI with some out-of-the-box skinning.
Let's start off by taking a look at the ShadCN component library. The ShadCN component library is loosely, I'd say, affiliated with Next. I think the guy that actually is the primary contributor on ShadCN works at Vercel. I don't think it's in any other way coupled to it. The unique thing about ShadCN is how it's installed and how it's updated.
When you create a Next.js application, you can use this ShadCN init command, and what that does is actually copy in code into your application. Then As you add button and input and any other of the components that ShazCN has, those are copied in to your application. So it's kind of a little controversial in that way, but there are some advantages. So one thing is that you can go and modify the theme, the way it does, for example, a button simply by editing the code that it copied into your application. Now, if you're thinking, oh, wow, it's going to copy a lot of code in my application, it's really not because ShazCN actually sits on top of Radix primarily.
It sits on a couple other headless libraries as well, but primarily Radix. So the button wrapper is very light. The select is very light. Radix is a headless UI. You basically just give it the classes, you handle the styling, and it handles all the rest.
So really, it's a pretty light wrapper on top of Redix. All right, let's go take a look at how to implement our blog using ShadCN. So I'm going to start off by creating a next app called ShadCN test. The important part here is I'm going to use Tailwind CSS. ShadCN relies on Tailwind.
So you want to say yes here for Tailwind CSS. Other than that, everything else is up to you. Well, except of course the app writer, you want to use the app writer. All right, let's bring that up in VS Code. All right, now that we got our ShadCN up in VS Code, let's go and initialize ShadCN by running the ShadCN init command.
To do that, I use mpx. Mpx is one way to run commands from packages. In this case, I'm going to use the init command from ShadCN at the latest version. So that's ShadCN at latest, followed by init. Init initializes the repo to add ShadCN.
We use the default look. We'll use CSS variables for colors. And that's it. Now we are ready to use ShadCN. So I'm gonna get the layout set up first.
Let's go over to our source app layout. And in there, I'm just personally going to set us into dark mode. That's just as easy as specifying that we want dark as our class for our body. And I'm gonna add a little padding just to bring it in off the side, so that's P5. Now, before we get the layout going, I gotta get the infrastructure of the app going.
So let's go over here to our source directory, and I'm going to bring in the support for the blog infrastructure, and we'll walk through that. So over in the instructions, there are two files. The exported type from types is simply just blog entry, and a blog entry is an object that has a title and some text. The title of the blog entry and then the text. And then we have the blog file.
Both of these are going to be in the source directory so it's at slash blog and at slash types and the blog system is in memory. So we're gonna have this const entries. It's gonna have our list of blog entries. We can add new blog entries to it. And then we have two async functions, getEntries and addEntry.
GetEntries just simply returns the list of entries and then adding an entry just pushes the new entry onto the in-memory list and revalidates the path. Now, why are these async? They clearly don't need to be async because there's nothing async going on in them. Well, they're async because these are server actions. This is in a file that has use server at the top.
That means that both get entries and add entry are server actions. All right, let's hit save. Now that we have those two files down, now we can actually start building out the left-hand panel that lists our blog entries. Now to do that, I'm going to create a new component in app, we'll call it blog, and it's going to be a React server component in this case because it doesn't have useClient at the top of the file. It's gonna take entries, which is a read-only list of entries.
And then we're gonna iterate through those entries and create H1s for every title and divs for every entry text. And we're just going to use out of the box Tailwind styling for this. That's the really nice thing about ShadCN. When it comes to your typography and your margins and your padding and your layout, you're just going to use Tailwind for that. It's only components like input and button that actually use any kind of external stuff, like in this case, the Radix UI to create interactive components that are also accessible.
All right, now that we got that, let's go over to our main page and I'm going to pair this down to just the home component and I'm going to bring in that blog that we just created. I'm also going to bring in the get entries function from at blog that we just created. That get entries gives us a list of blog entries. Then we'll get the entries and thanks Copilot for the hinting there, but I do want to put a little bit more around this. I'm going to give us a flex box as the primary layout mechanism by default.
Flex is a flex row. So I'm going to give a half column to the entries, then I'm going to give a half column to nothing at this point. We're going to add the form in just a second, but that's what's going to go in the other column. Now, let's go and do our PMPM dev and take a look and see what this looks like. All right, that looks pretty good.
Okay. So now in order to do the forms, now we actually need to bring in some ShadCN UI components. So do that. We again use the ShadCN package, but this time we use the adverb, and then we give it button, input, and form. Those are the three components that we need for our application.
Now, You can see that it's installing each one of those. Actually installed an extra one label because form depends on label. And if you want to see where those go, well, those go in the components UI directory. And let's take something like label. You can see that this is a fairly short file.
We get the React label from RADX UI, and then we're just skinning that with our own classes, and that's under the label variant. So if you wanna change label, you just change it right here, and that becomes your new label. All right, now let's go create our blog form. So we'll create another file, blog form, and we'll make it a client component, because it is going to be interactive. So create our default blog form component.
Now we'll start off by getting the connection to the back end. That's add entry from blog that's that server action for adding an entry. We're just going to use a server action to post the new blog entry to the blog. We're gonna spec out what our form fields look like for our form system. Again, that's just the title and the text, just like we had with the blog entry.
And then we'll bring in our components from ShadCN. We also need to bring in React Hook Form because React Hook Form in this case is going to be what we use to manage the form. This is not in every case. A lot of the component libraries have their own forms management system and when they do we'll use that one instead. In this case, we want to use React Hook form.
So let's bring in that. Now, of course, you could use Formic or any other kind of forms management system. I just find that in my experience, a lot of folks are really happy with React Hook form. So speaking of React Hook form, let's go and use React Hook form to create our form. So we use that form fields type to say what the form fields are, and we give it some default values, and then we use the use form hook from React Hook form.
Now we'll create a non-submit handler. This is gonna get called when we submit the form to the server. So it's gonna call our add entry server action with the data that we get. The data that we get from React-Hook form is just the form fields. And then when we've done our submission, we reset the form.
This is exactly the same submit handle you'll see in pretty much every example. All right, now let's get into actually building out our form. To do that, we use the form component from Radix, and then that wraps a regular HTML form. We give that HTML form our submit handler. So we use form.handleSubmit.
That's actually what's gonna do the form validation. And then if the validation passes, it'll call our onSubmit, which will then submit to the server. We're also going to give it some nice tailwind layout, in this case a flex column with a little gap between each one, so we get some nice spacing between the inputs. Now the easiest piece of UI to add is the submit button, so let's go ahead and add that. We're going to use the off the shelf button and we'll just put a type on it.
So now let's actually just bring that in and see what it looks like right now. All right, we'll bring in blog form, what we put up there, handle my OCD, and then we will put it in here. All right, let's go and run this thing. And there we go. Now I've got our big old submit button there.
So everything looks good, but we actually need some fields to put our blog entry into. So let's go and add those. All right, before we start, our submit button actually has to be inside of our form. That would help. And now let's talk about React Hook Form for a second.
React Hook Form is agnostic to any type of component library or CSS system that you might be dealing with. So what it's got is it's got a way to generically connect a rendering component in this case like input to a field and that mechanism is a controller. So we're gonna get a controller. So we're gonna use the controller component up here from React Hook Form. And the controller takes the name of the field that you want to manage, in this case title, takes the control function from Form, and then you give it a render function.
And the render function gives you everything you need to pass on to, in this case, an input. So inside here, we're going to render out all the ShadCN stuff. ShadCN defines a form item. That form item has a form label, a form control, and then it's got the generic input from components UI input. And then we take that field we got from render and then we splatted in to our input.
I also save. And now we can see that we got our title And that looks great. Okay, now let's do the same thing for our text. So to do that, I'm just gonna copy this and paste it. I'm gonna change this to text.
For humans, we call this content. Otherwise, there we go. Let's go take a look. So we got our title, we got our content. I'm gonna drop something in here.
Some title, some content. Hit submit and there you go. And that's it. Now you understand how ShadCN works for basic primitives like inputs and buttons, how it does its layout. In this case, you're just going to use basic Tailwind for its layout, how it does dark mode, you're just going to use the basic Tailwind for dark mode.
It's just a nice system that integrates really well with Tailwind. So if you're a Tailwind fan, if your applications are on Tailwind, I would definitely recommend looking at ShadCN if you want really good accessible UI components on top of Tailwind. And really what you're looking at is Radek's UI with some out-of-the-box skinning.