DEV Community

Arsalan Ahmed Yaldram
Arsalan Ahmed Yaldram

Posted on • Edited on

Building a design system with dark mode using React, Typescript, scss, cva and Vite - Setup

Introduction

In my previous design system tutorial series taking inspiration from chakra-ui we created a design system using React, Typescript, styled-components and styled-system. But css-in-js libraries like styled-components have a performance penalty, and one should avoid using css-in-js libraries if you are using Server Side Rendering. In this tutorial series we will re-create our design system but this time using React, Typescript, Scss and class-variance-authority. I would encourage you to play around with the deployed storybook. All the code for this series is available on GitHub.

Step One: Bootstrap the component library

I won't go into the details; I would request you to check my other tutorial on creating a component library from scratch using Vite. From your terminal -

mkdir react-scss-chakra-ui 
cd react-scss-chakra-ui 
git init
git remote add origin https://github.com/username/repo.git
Enter fullscreen mode Exit fullscreen mode

Now after running npm init here is my package.json -

{
  "name": "@yaldram/react-scss-chakra-ui",
  "version": "0.0.1",
  "description": "A chakra clone design-system using react, scss, class-variance-authority, only static styles, no css in js.",
  "source": "src/index.ts",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/yaldram/react-scss-chakra-ui.git"
  },
  "keywords": [
    "react",
    "scss",
    "class-variance-authority"
  ],
  "author": "Arsalan Yaldram.",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/yaldram/react-scss-chakra-ui/issues"
  },
  "homepage": "https://github.com/yaldram/react-scss-chakra-ui#readme",
}
Enter fullscreen mode Exit fullscreen mode

Now lets install our dev dependencies, for building and bundling our project. We require the following -

yarn add -D typescript @types/node sass vite @rollup/plugin-typescript tslib
Enter fullscreen mode Exit fullscreen mode
  • We are installing sass the complier for scss.
  • We are installing vite which will build and bundle our library.
  • We are installing @rollup/plugin-typescript & tslib for generating the type def files.

Now let us install our peer dependencies from your terminal run -

yarn add -D react react-dom @types/react @types/react-dom
Enter fullscreen mode Exit fullscreen mode

Make sure you add react and react-dom to peerDependencies in your package.json because we don't want to bundle them with our library.

"peerDependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
 }
Enter fullscreen mode Exit fullscreen mode

Finally we install cva as a dependency -

yarn add class-variance-authority
Enter fullscreen mode Exit fullscreen mode

Step Two: Setup vite

Now from the root of our project create a tsconfig.json -

{
  "compilerOptions": {
    "outDir": "dist",
    "module": "esnext",
    "target": "ESNext",
    "lib": ["dom", "esnext"],
    "moduleResolution": "node",
    "jsx": "react",
    "sourceMap": true,
    "declaration": true,
    "esModuleInterop": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "allowSyntheticDefaultImports": true,
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist", "**/*.stories.tsx", "**/*.test.tsx"]
}
Enter fullscreen mode Exit fullscreen mode

Again from the root of our project create a new file vite.config.ts -

import { defineConfig } from "vite";
import typescript from "@rollup/plugin-typescript";
import { resolve } from "path";

import { peerDependencies } from "./package.json";

export default defineConfig({
  build: {
    outDir: "dist",
    sourcemap: true,
    lib: {
      entry: resolve(__dirname, "src/index.ts"),
      formats: ["es"],
      fileName: "index",
    },

    rollupOptions: {
      external: [...Object.keys(peerDependencies)],
      plugins: [typescript({ tsconfig: "./tsconfig.json" })],
      output: {
        assetFileNames: ({ name = "" }) => {
          /**
           * Vite gives us a single style.css file
           * in the build we are moving it to the css
           * folder and renaming it to main.css
           */
          return name === "style.css" ? "css/main.css" : name;
        },
      },
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

Finally, we need to make some changes under the package.json file -

 "main": "./dist/index.js",
 "types": "./dist/index.d.ts",
 "source": "src/index.ts",
 "files": [
    "dist"
 ],
 "exports": {
   ".": {
     "import": "./dist/index.mjs"
   },
   "./dist/css/main.css": {
     "import": "./dist/css/main.css"
   }
 },
 "scripts": {
    "build": "tsc && vite build"
 },
Enter fullscreen mode Exit fullscreen mode

Take a note of the exports entry in the package.json file : -

  1. The first . entry is for importing our components import { Box } from "@yaldram/react-scss-chakra-ui".
  2. The second entry is for importing the css from our library - import "@yaldram/react-scss-chakra-ui/dist/css/main.css".

Step Three: Storybook setup

We will use the latest and greatest storybook version 7 -

npx sb@next init
Enter fullscreen mode Exit fullscreen mode

Under .storybook/preview-head.html paste the following -

<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
  href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;900&display=swap"
  rel="stylesheet"
/>
<style>
  * {
    padding: 0;
    margin: 0;
  }

  body {
    font-family: Montserrat, sans-serif;
  }
</style>

<script>
  window.global = window;
</script>
Enter fullscreen mode Exit fullscreen mode

Under .storybook/main.ts paste the following -

import type { StorybookConfig } from "@storybook/react-vite";

const config: StorybookConfig = {
  stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions",
  ],
  framework: {
    name: "@storybook/react-vite",
    options: {},
  },
  docs: {
    autodocs: true,
  },
};

export default config;
Enter fullscreen mode Exit fullscreen mode

Finally under .storybook folder rename the preview.ts file to preview.tsx.

Step Five: Folder structure

While building components, I follow the atomic design methodology. Create a src folder, inside it create a new file index.ts. Now create a components folder, create 2 folders inside it atoms & molecules.

Now inside the atoms folder create a index.ts file, and create a new folder layouts. Finally, inside layouts create an index.ts file and create 2 folders box & flex. Your directory structure should look like

directory-structure

Now inside the box folder create a new file index.tsx -

import * as React from "react";

export interface BoxProps extends React.ComponentPropsWithoutRef<"div"> {
  children?: React.ReactNode;
}

export function Box(props: BoxProps) {
  return <div {...props} />;
}
Enter fullscreen mode Exit fullscreen mode

Create a new file for the story box.stories.tsx -

import * as React from "react";

import { Box } from ".";

export default {
  title: "Atoms/Layout/Box",
};

export const Default = () => <Box>This is a Box Component</Box>;
Enter fullscreen mode Exit fullscreen mode

Now from your terminal run yarn storybook and check the output.

Let's export our Box component under components/atoms/layouts/index.ts -

export * from "./box";
Enter fullscreen mode Exit fullscreen mode

Under /components/atoms/index.ts -

export * from "./layouts";
Enter fullscreen mode Exit fullscreen mode

Finally under src/index.ts paste the following -

export * from "./components/atoms";
Enter fullscreen mode Exit fullscreen mode

From the terminal, build the project by running -

yarn build
Enter fullscreen mode Exit fullscreen mode

It should output a dist folder with an index.mjs file along with our typescript definition files. You can also test our library by publishing it and importing the Box component in a new React project. Read more on publishing here.

Conclusion

In this tutorial we added the base setup of our design system library. All the code for this tutorial can be found here. In the next tutorial we will work with scss adding our design tokens (colors, spacing, line-height values). Until next time PEACE.

Top comments (0)