FYI: The above mentioned YouTube Video is in Hindi Language.
If you want to get a frontend job, you need to master how to build a Design System from scratch or atleast you know how to build it. Because almost every company is trying to build a design system these days. So in this series we'll exactly build this from scratch.
Tech Stack
We'll be using the following tech stack to build the Design System:
- React
- Storybook
- Rollup
- TypeScript
- Framer Motion
- Styled-Components
- xStyled
- Shadcn
Reasons to Choose the Above Tech Stack
React
will always be my first priority. We need to showcase our component library to other developers or community, for that we'll be using Storybook
. There are multiple options for bundling the package e.g. tsup, webpack etc, but these days Rollup
is very popular so I am choosing that one and to be very honest I am very comfortable using this bundler so that's why I choose Rollup over other JavaScript bundlers. We need animations for some components, we can build all those animations from scratch or just using css but sometimes by just using css we make our life hell so I am choosing Framer Motion
for that.
Now how we style our components that's the main point, we have couple of options:
- we can use pure CSS for that but its hard to manage
- tailwind css - but I am not comfortable writing it - so skipping it
- CSS Modules - its hard to maintain I guess
- CSS in JS - we are using this one
There are multiple CSS in JS libraries as well e.g. Emotion, Styled-Components etc.. I picked Styled-Components
that's what I am using it for past few years. But its very hard to setup theme, we can do that setup but we have to create it from scratch e.g. Theme, Theme Tokens etc.. For that we'll be using xStyled. xStyled
built by the same team that build styled-components
.
Source Code
If you want the Source Code, you can use this GitHub Repository, all the code will be there.
Badge Component
This is what we are trying to build it.
And this is what we've built. This is the Final Preview.
Types
First we need to decide what props we should use, in our case we'll be using the following:
- BadgeSize
- BadgeColor
- Any JSX Content i.e. children
export type BadgeSize = "sm" | "md" | "lg";
export type BadgeColor = "neutral" | "error" | "warning" | "success" | "primary";
type Props = {
children: React.ReactNode;
size?: BadgeSize;
color?: BadgeColor;
};
Badge Styles
Next we need to define all the styles related to Badge
"use client";
import styled, { css } from "@xstyled/styled-components";
import { BadgeColor, BadgeSize } from "./types";
const badgeSizes = {
sm: css`
padding: 2px 6px;
font-size: 0.75rem;
`,
md: css`
padding: 2px 8px;
font-size: 0.875rem;
`,
lg: css`
padding: 4px 10px;
font-size: 0.875rem;
`,
};
const badgeColors = {
neutral: css`
border: 1px solid #e6e6e6;
background-color: #f9fafb;
color: #525252;
`,
error: css`
border: 1px solid #fecaca;
background-color: #fef2f2;
color: #dc2626;
`,
warning: css`
border: 1px solid #fde68a;
background-color: #fffbeb;
color: #b45309;
`,
success: css`
border: 1px solid #bbf7d0;
background-color: #f0fdf4;
color: #15803d;
`,
primary: css`
border: 1px solid #c7d2fe;
background-color: #eef2ff;
color: #4338ca;
`,
};
export const Badge = styled.span<{ $size: BadgeSize; $color: BadgeColor }>`
text-align: center;
border-radius: 9999px;
${(p) => badgeSizes[p.$size]};
${(p) => badgeColors[p.$color]};
`;
As you can see, in the top I added the use client
directive. We are marking at client component otherwise you'll get an error if you are using nextjs.
For BadgeSize
, I added the styles for each variants separately so that we can easily maintain it. And I did same for BadgeColor
. And in the last I added the common styles that should use in the all the variants, I added in the main Badge Styles. Also I am conditionally extract styles from the badgeSizes
and badgeColors
.
Badge Component
Now I just need to render the Badge Component. Here's how I am rendering it:
import { forwardRef } from "react";
import { Badge as BadgeStyle } from "./styles";
import { BadgeColor, BadgeSize } from "./types";
type Props = {
children: React.ReactNode;
size?: BadgeSize;
color?: BadgeColor;
};
const Badge = forwardRef(function Badge(
{ children, size = "md", color = "neutral" }: Props,
ref: any,
) {
return <BadgeStyle ref={ref} $size={size} $color={color} children={children} />;
});
export { Badge };
Its best practice to wrap your component with the ref so that the developer who is using your component library doesn't need to wrap your component with another component just for ref. Because in many cases, we need to pass ref e.g. tooltip. I am also delegating all the rest props to the Badge Component so that developer can pass any additional props if they want.
StoryBook
Last but not least, we need to add StoryBook for our Badge Component so that we can showcase it easily and we also want to test it. Here's how you can create a Badge Component StoryBook:
import type { Meta, StoryObj } from "@storybook/react";
import { Badge } from "../components/Badge";
const meta: Meta<typeof Badge> = {
title: "Badge",
component: Badge,
tags: ["autodocs"],
parameters: {
layout: "fullscreen",
},
decorators: [
(story) => (
<div style={{ display: "flex", justifyContent: "center", margin: "20px" }}>{story()}</div>
),
],
};
export default meta;
type Story = StoryObj<typeof meta>;
export const General: Story = {
args: {
children: "Badge",
},
};
For each individual story, you need to named export it whatever you like. You can create as much stories as you want. So that's pretty much it.
Conclusion
Its always a good idea to build a design system from scratch so that you know how design system actually built and how much developers are putting efforts into it. And by the time, you'll learn it how fun it is to make a design system.
FYI, all the components that I'll build in this series comes from the GreatFrontend UI Designs.
Top comments (0)