Implementing Your Own Design System in Next.js
Heads up!
This summary and transcript were automatically generated using AI with the Free YouTube Transcript Summary Tool by LunaNotes.
Generate a summary for freeIf you found this summary useful, consider buying us a coffee. It would help us a lot!
Introduction
Welcome to the world of design systems! In this detailed guide, we will explore the steps to implement your very own design system using Next.js. We’ll cover key concepts, tools, and techniques that allow for seamless collaboration between designers and engineers while maintaining a high degree of customization and accessibility in your projects.
What is a Design System?
A design system is essentially a comprehensive library of reusable UI components and design tokens, including colors and typography. It acts as a bridge between designers, who use tools like Figma or Sketch, and engineers, who implement these designs using frameworks such as Next.js.
Why Build Your Own Design System?
- Customization: Existing UI libraries like Material-UI or ChakraUI may not align with your design vision.
- Consistency: A custom design system ensures that elements are coherent across your applications.
- Flexibility: You're free to create components that match your project’s unique requirements.
The Benefits of Using Next.js for Your Design System
Next.js offers powerful features such as server-side rendering, static site generation, and a rich ecosystem. These capabilities boost performance, improve SEO, and provide excellent developer experience.
Implementing Your Design System
Choosing the Right Tools
To build your design system effectively, we will use:
- Tailwind CSS: A utility-first CSS framework that allows for rapid component styling and customization.
- Class Variance Authority (CVA): A tool to manage complex component styling using variants.
- Storybook: An isolated environment for developing and testing components.
- Headless UI: For creating accessible components without compromising on design.
Step 1: Setting Up Tailwind CSS
Tailwind CSS is fully customizable and offers comprehensive documentation. To start:
- Install Tailwind CSS in your Next.js project.
- Configure your
tailwind.config.js
to define your brand colors, font sizes, and other styles. - Example configuration:
module.exports = { theme: { extend: { colors: { brand: '#ff5733', }, }, }, variants: {}, plugins: [], };
Step 2: Creating Reusable Components
Next, let’s define our UI components. Start with a button component:
import { cva } from 'class-variance-authority';
const buttonStyles = cva('px-4 py-2 font-semibold text-sm rounded', {
variants: {
intent: {
primary: 'bg-blue-500 text-white',
secondary: 'bg-gray-300 text-black',
danger: 'bg-red-500 text-white',
},
fullWidth: {
true: 'w-full',
false: 'w-auto',
},
},
defaultVariants: {
intent: 'primary',
},
});
function Button({ intent, fullWidth, children }) {
return <button className={buttonStyles({ intent, fullWidth })}>{children}</button>;
}
Step 3: Managing Variants with CVA
Implement Class Variance Authority (CVA) to simplify your component styles:
- Defines
variants
that apply conditional styles based on props (e.g., button size, color). - Automatically handles default variations for your components.
Step 4: Ensuring Accessibility
Utilize libraries like Headless UI to make your components accessible. For example, an accessible dropdown can be created:
import { Menu } from '@headlessui/react';
import { Button } from './Button';
function Dropdown() {
return (
<Menu>
<Menu.Button as={Button} intent="primary">Actions</Menu.Button>
<Menu.Items>
<Menu.Item>
{({ active }) => (
<a className={active ? 'bg-blue-500' : ''}>Create Note</a>
)}
</Menu.Item>
</Menu.Items>
</Menu>
);
}
Step 5: Testing Components in Storybook
Integrate Storybook as a development environment:
- Install Storybook in your Next.js project.
- Create stories for each component:
import Button from './Button'; export default { title: 'UI/Button', component: Button }; export const Primary = () => <Button intent="primary">Primary Button</Button>;
Now you can run pnpm run storybook
, and see all your components rendered with different props without risking your main application flow.
Conclusion
In this guide, we learned how to implement a robust design system in Next.js using Tailwind CSS, Class Variance Authority, and Storybook. By building your own design system, you maintain strong control over the visual identity of your applications, ensure accessibility, and enhance the overall development experience. Remember, the key is to build only what you need and maintain your design system to avoid tech debt. Stay ahead in the game by embracing a custom approach that aligns perfectly with your design vision!
Thank you for joining me on this deep dive into design systems. I’m excited to see what you create! For more tips and tutorials, connect with me online!
internet and I'm a software engineer at Discord I'm also kind of a big deal on the internet
I've been using nextgs for a while now and it's super exciting to be able to talk at this conference today I'll be
telling you a haunting tale about how to implement your own design system in nexjs before we start talking about how
to implement a design system we first have to Define what a design system is a design system is a set of reusable UI
components and design tokens so like colors and fonts that bridge the gap between designers and Engineers
designers use tools like figma or sketch to design UI components that can be reusable across multiple projects and
still look cohesive whereas Engineers Implement those components in whatever framework that they choose using various
tools so here's an example of a design System created in figma currently there is a couple of ways to implement Design
Systems within your project you might be familiar with some existing component libraries such as manteam chakra mui Etc
and as good as those UI libraries are they're also very opinionated so if your design team has designs in mind that are
very different from what you get out of the box with those existing UI component libraries you'll have to go in and write
a lot of code sometimes this is fine but this could get really tedious and annoying if the designs are very
unaligned the other option instead of using something pre-built is to build your own design system from scratch this
allows you to be as opinionated as you want and easily match the existing design System since you have to build it
all yourself however nice this seems implementing your own design system can be overwhelming you have to define a lot
of styles figure out accessibility pick how opinionated that you want to be and you have to maintain the system so that
you don't have Tech debt thankfully however hard it may seem the Tooling in this area has become much better another
thing to keep in mind is you only have to build out the components that you need in your design system a lot of
these existing UI component libraries that I mentioned earlier come pre-built with so many different components and
you only use like a handful of them most of the time so the scope of building your own design system isn't nearly as
as big as building out one of these open source UI component Library so here's a way that I found that might help you
implement a design system there's a couple of key technologies that we'll use here and I'll talk through all of
them so the first is talent I was actually very anti-tailwind in the beginning and I started using it on
stream and I really like the ease of use of using tailwind and also how I don't know it's just so easy to bring
into any project and quickly get it up and running the reason Tailwind is really amazing for this specific problem
is that it's fully customizable to your theme out of the box Tailwind comes with a set of font sizes color themes and
spacing increments this can be useful but when implementing a design system you could have conflicts thankfully
Tailwind is fully customizable you can overwrite any of these existing presets so since Tailwind is open source you can
actually take a look at their default config and basically this is a stub for all the different things that you can
change within your Tailwind config so here you can see the different size screens for responsive design you can
also see all the colors you can even change the spacing there's just so much here that you could modify within your
own tail and config to make it easier for you to use so if you actually look at the Tailwind config that I have in my
project here I'm actually just defining a brand color and so this brand color I can then use
as one of the talent colors so instead of saying you know text Gray I can say text brand and it shows the actual brand
color that I Define you can also change default colors so here I am changing the default ring color to be the brand color
because of this you can truly build anything with Tailwind Tailwind gives us the ability to Define our Styles but how
do we make this work within our components components will accept props and we need to conditionally assign
classes based on these props that are passed in a common solution to this is to use something like class names or
clsx so here is an old button that I had that uses clsx to conditionally apply classes now if you see here the props
for this button basically had an intent and that is what I want the button to look like so here there's a primary
secondary and a danger button and so in the component button I'm grabbing the props and I'm setting the initial state
to intent to primary and now we use clsx here in the class name to basically combine the classes
together based on the conditions that are being applied so clsx first takes in default class names so these are just
all the tail and classes that are being applied no matter what the prop is that's being passed in and then here you
can see that based on whatever the intent is we're changing the background color and the text color of these
buttons so you can see that if we only have one prop that's being passed in it's not that bad but this could get
really complicated if your button takes in like 10 or 12 props this is where class variance Authority or CVA comes in
CVA gives you an easy to use interface to Define variance which conditionally apply sets of classes CVA can also
Express default variance and even compound variants where classes can be applied based on a combination of
variants as a nice side effect the typescript type for variance is also super easy to incorporate into your
component using the variant props type helper we'll take a closer look at this during the demo the other thing to keep
in mind when you're building out your design system is to account for accessibility thankfully there's a lot
of great Solutions in the space I'll be working with headless UI but there's also things like react Aria and Radix UI
which are also great libraries worth looking into when we're developing with our design system we can also use a few
techniques to make our life easier with typescript we can set up a path Alias so this lets us import our UI components
from a consistent path instead of a relative path I can set up at UI slash to basically point to the folder that
our UI components are in so here in my ts config I'm setting a path and I'm setting at UI slash asterisk to point to
the source components UI folder so now if we go take a look at our note component over here we can see that when
we're importing all these components from our UI folder that the path is now at UI slash and whatever the path name
is so it makes it super clean and easy to use if you're using a mono repo this can also help because you can Define
your UI components as a shared internal package and so the last tool we're going to bring in is storybook storybook is an
isolated component playground where we can test our components outside the context of our application this is
critical when building UI component libraries because it lets you avoid having to run the entire app to test all
the states of different components that you build for example you could have a delete button that only appears on a
specific page and so in storybook what you can do is just render the delete button component and be able to change
the props however you please and see how it looks based on the different props and now here's the demo let's get into
here in my source file I have my components and in my components folder I have my UI so here you can see things
like button form input menu Etc so let's take a look at the code specifically for these buttons that you see here so we
looked earlier at a way to do this with clsx and passing in different props and then changing the class names based on
those props if we take a look at button you can see that here I'm importing in CVA from class variance Authority so
here I'm defining button styles by basically calling CVA and then passing in default Styles but the cool thing
about CVA is we have these variants so here I have a variant and in there I have this thing called intent and so
here when I have the primary secondary in danger intent here's the styles that would get applied based on whichever
intent is being passed through not only can you do variants based on the use different words you can also do Boolean
variants so here I have a full width variant that's basically saying if this prop is passed in then make the button
full width here I'm only passing in true but you could also pass in a class name that would get applied if this was set
to false the other cool thing with CVA is you can Define the default variance as I mentioned previously so here what
I'm saying is if there's no intent being passed in default the variant to primary so now if we scroll down and we take a
look at this component you can see our props extend button or link props which are basic button props and then we have
this type variant props which basically take in the type of button Styles you can see here all of our types properly
typed so when I go into the button component and I'm actually grabbing props I can actually extract intent and
you can see here the type of intent is primary secondary danger and the type of full width is a Boolean so if I do type
in an intent you know let's say it's like tertiary this typescript will yell at you because it's not a valid type
when you render in whatever component you want for the class name all you'd have to do is pass through the button
styles with the intent and full width and any other props so now that we've seen what a button created with CVA and
so here we have the code running and there is a login button a sign up button and a view your notes button that
basically are all the same button component but with different props passed in so if we go take a look at
these here we have the button and the intent of the login and sign up button is secondary but I can just come in here
and change it to primary and you can see that it changes something that I mentioned earlier was
accessibility and so here I use headless UI for my accessible components so if you look at our website we have this new
note button and there's a drop down and I can access the different things without clicking via the arrow keys so
how do we build this drop down using headless UI so if we go back to the code as you can see here we use this new note
button and it leverages the menu the menu button and the menu items Etc from headless UI so if we look at the menu
implementation here in our UI components folder we basically just leverage headless ui's component the menu
component and just add a couple of features on top of that and so this is pretty default implementation I do use
clsx here for the button or link this is basically the items in the drop down I haven't really fully fleshed out all
these components with CVA yet but this is somewhere where you could add that as well so now if we take a look back at R
note page you can see here that we're rendering this menu button and what we can do is just pass in as and then our
button so what this does is this actually pulls in that button that we've made that uses CVA and so the really
cool thing is this menu button now that you've declared it as this button you can actually grab the intent and set the
prop immediately here so currently I think it's set to primary because that's the default so let's do it to secondary
and see what happens and if we look back at our website you can see that it's now gray instead of
your design system and now let's look at one last thing storybook so storybook is pretty easy to implement
so if you look here in our UI folder you can see that we have a button.stories.tsx and here's like sort
of a description on what this story looks like so you pass in a title you pass in the component and you pass
in the ARG types that you want to change and so down here we basically render that button that component that you want
to put in your storybook and we pass in the arguments and now here what we basically do is we have different
options so we can set a primary we can set a secondary we can set a danger and these just take in our different intents
and so let's run storybook so we can do pnpm run storybook and it takes a little bit to run
but once it's open you can see that it opens up here so you know how we said UI button so it's under UI under button and
then under the button we have different versions we have our primary we have our secondary and we have our danger and the
cool part is you can actually just change them here as well but you set like default values so that's what this
is doing and you can also set the Boolean for full width so you can set this to true and see what happens set it
back to false and see what happens so storybook makes it super easy you don't have to render these buttons in your
actual application to see what they would look like with all the different props that you pass in and so we also do
menu in this way so here we have the menu button and we can actually see what the drop down looks like without
actually going to that page that notes page and using the drop down there and our menu stories look very similar
to our button stories so here we basically are setting the title to UI menu which component it is and then we
are just rendering the menu passing in those arguments as we did to the button as well hope you
like this talk thank you so much for watching and if you're interested in more from me I'm you true on Twitch on
YouTube what else on tick tock on Instagram and unfortunately not on Twitter but I'm true narl on Twitter so