Building and Publishing Design Systems | Part 2

Learn how to build and publish design systems effectively. Discover best practices for creating reusable components and enhancing UI consistency.

This blogpost is a part of the "Building Design System and Micro Frontends" series. For more content, read the first post. The source code for all parts of this series can be found in this GitHub repository.

For building our design system, we will use react, tailwind, storybook and webpack.

The blogposts of this series
1. Building design system and micro frontends
2. Building and publishing a design system (you are here)
3. Building micro frontends consuming a design system
4. Building a shell application for micro frontends

 

A few words why I selected these technologies

  • React
    Proven, stable, excellent component composition system, focus on back-compatibility. Read the "Design Principles" post on reacts blog; this post outlines react development guiding principles.
     

  • Tailwind
    Gives us a simple way to define seed for the design system. Sizes/Distances scale, colour palettes, shadows, responsive breakpoints, transitions and animations etc., all can be defined succinctly. It also gives us ultimate style isolation and a big productivity boost.
     

  • Storybook
    Helps us develop components in isolation. Helps in component testing and, when exported, delivers portable documentation.
     

  • Webpack
    Proven, stable, vivid plugin ecosystem; a lot of materials.

Setting up a Design System Project

Create directory design-system and run the following commands from it:

npm init -y will setup package.json file.

Run npm run storybook to build and run storybook. It should bind to port 6006. Open http://localhost:6006. You should see some example stories.

The npx sb init command takes care of setting up the storybook. It will create a stories directory with examples in the project root directory. You can remove its content. We will create a simple story later from scratch.

 

Settin up Tailwind

npm i -D [email protected]  [email protected]
npx tailwindcss init

npx tailwindcss init will create ./tailwind.config.js file with the default configuration.

Open ./tailwind.config.js Add mode property and set purge patterns (lines 2,3).

module.exports = {
  mode: "jit",
  purge: ["./src/**/*.js", "./src/**/*.html"],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: 
  ,
  variants: {
    extend: 
  ,
  plugins: [],
}

Create ./postcss.config.js in root directory.

Paste following code in ./postcss.config.js:

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {}
  
}

This configuration file adds tailwindcss and autoprefixer plugins to PostCss processing pipeline.

 

Setting up Tailwind for Storybook

In the next two steps we will set up tailwind for storybook. We need to install appropriate plugin and configure it.

Install @storybook/addon-postcss addon for storybook.

npm i -D @storybook/[email protected]

Update ./.storybook/main.js; add highlighted configuration to addons array:

module.exports = {
  stories: [
    "../stories/**/*.stories.mdx",
    "../stories/**/*.stories.@(js|jsx|ts|tsx)",
  ],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    {
      name: "@storybook/addon-postcss",
      options: {
        postcssLoaderOptions: {
          implementation: require("postcss"),
        },
      },
    }
  ],
  core: {
    builder: "webpack5",
  },
};

Create file ./src/styles.css.

Paste following code in ./src/styles.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

Open ./.storybook/preview.js and add import styles (line 1).

import '../src/styles.css'

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
}

Create Component and Story

Create Button.js file in ./src/components with the following content

import React from "react";

export default function Button({label, disabled, children}) {
  return (
    
  );
}

In ./stories directory create `Button.stories.js` file with the following content:

import React from 'react';

import Button from '../src/components/Button';

export default {
    title: 'Example/Button',
    component: Button,
    argTypes: {
        label: {control: 'text'},
        disabled: {control: 'boolean'}
    },
};

const Template = (args) => 

Run npm run storybook. Open http://localhost:6006. You should see something like this:

Building Design System Package

It is time to create production build of our design system. It should contain only necessary code. We will exclude react from the build. The assumption here is that whoever is consuming design system will be responsible for including react.

In a real project design system should be packaged and published to a npm repository so the other project can use npm to get it. However, for this walkthrough, we will build it and keep it in a directory on the disk. Other projects that we build in the next post will consume it from the disk.

Create an entry point for our library; Create ./src/index.js and add the following content:

import './styles.css'
import Button from "./components/Button";

export   

Every component in our design system must be imported and re-exported in this file.

Install following packages:

Create file ./.babelrc with the following content:

{
  presets: [
    [
      '@babel/preset-env',
      {
        modules: false
      }
    ],
    '@babel/preset-react'
  ],
  plugins: []
}

Create file ./webpack.config.js with the following content:

const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const config = {
  entry: [
    './src/index.js'
  ],
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    library: {
      name: "fitness",
      type: "umd"
    }
  },
  externals: {
    "react": "react",
    "react-dom": "react-dom"
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'
        ]
      }
    ]
  },
  resolve: {
    extensions: [
      '.js',
      '.jsx',
      '.css'
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css'
    })
  ],
  optimization: {
    minimizer: [
      new CssMinimizerPlugin()
    ]
  }
};

module.exports = (env, argv) => {
  config.output.filename = '[name].js';
  return config;
};

There is a lot of things going on in this file; so lets break it out:

  • Line 6 - we define an entry point for our library, all components exported in this file will be available for consumers.

  • Line 9 - the output of the build will be saved to dist folder

  • Lines 11-14 - we configure webpack to create a library and use UMD for modules.

  • Lines 16-19 - we externalize react. This way it is not packaged with the library.

  • Lines 20-41 - standard configuration for building js, jsx and CSS

  • Lines 42-46 - add plugin for CSS extractions

  • Lines 47-52 - configure CSS minification

 

The last step is to add npm script; add following line to package.json scripts array:

"build-design-system": "cross-env NODE_ENV=production webpack --mode production"

To build the design system run npm run build-design-system.

There should be two files in the dist directory. main.js (5KB), and main.css (7KB).

Blog 7/15/21

Building a micro frontend consuming a design system | Part 3

In this blopgpost, you will learn how to create a react application that consumes a design system.

Blog 7/13/21

Composite UI with Design System and Micro Frontends

Discover how to create scalable composite UIs using design systems and micro-frontends. Enhance consistency and agility in your development process.

Blog 7/16/21

Building A Shell Application for Micro Frontends | Part 4

We already have a design system, several micro frontends consuming this design system, and now we need a shell application that imports micro frontends and displays them.

Blog 6/16/23

CSS :has() & Responsive Design

In my journey to tackle a responsive layout problem, I stumbled upon the remarkable benefits of the :has() pseudo-class. Initially, I attempted various other methods to resolve the issue, but ultimately, embracing the power of :has() proved to be the optimal solution. This blog explores my experience and highlights the advantages of utilizing the :has() pseudo-class in achieving flexible layouts.

Blog 9/13/22

Introduction to Functional Programming in F# – Part 2

Explore functions, types, and modules in F#. Enhance your skills with practical examples and insights in this detailed guide.

Blog 11/14/23

Part 2: Data Analysis with powerful Python

Analyzing and visualizing data from a SQLite database in Python can be a powerful way to gain insights and present your findings. In Part 2 of this blog series, we will walk you through the steps to retrieve data from a SQLite database file named gold.db and display it in the form of a chart using Python. We'll use some essential tools and libraries for this task.

Blog 3/11/21

Introduction to Web Programming in F# with Giraffe – Part 2

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 11/15/22

5 lessons from running a (remote) design systems book club

Last year I gifted a design systems book I had been reading to a friend and she suggested starting a mini book club so that she’d have some accountability to finish reading the book. I took her up on the offer and so in late spring, our design systems book club was born. But how can you make the meetings fun and engaging even though you're physically separated? Here are a couple of things I learned from running my very first remote book club with my friend!

Blog 10/21/20

Consistency and Aggregates in Event Sourcing

Learn how we ensures data consistency in event sourcing with effective use of aggregates, enhancing system reliability and performance.

Blog 7/21/20

Understanding F# applicatives and custom operators

In this post, Jonathan Channon, a newcomer to F#, discusses how he learnt about a slightly more advanced functional concept — Applicatives.

Blog 9/27/22

Creating solutions and projects in VS code

In this post we are going to create a new Solution containing an F# console project and a test project using the dotnet CLI in Visual Studio Code.

Blog 12/22/22

Introduction to Functional Programming in F# – Part 6

Learn error handling in F# with option types. Improve code reliability using F#'s powerful error-handling techniques.

Blog 12/22/22

Introduction to Functional Programming in F# – Part 7

Explore LINQ and query expressions in F#. Simplify data manipulation and enhance your functional programming skills with this guide.

Blog 5/17/23

Introduction to Functional Programming in F# – Part 10

Discover Agents and Mailboxes in F#. Build responsive applications using these powerful concurrency tools in functional programming.

Blog 8/8/23

Introduction to Functional Programming in F# – Part 12

Explore reflection and meta-programming in F#. Learn how to dynamically manipulate code and enhance flexibility with advanced techniques.

Blog 3/22/23

Introduction to Functional Programming in F# – Part 9

Explore Active Patterns and Computation Expressions in F#. Enhance code clarity and functionality with these advanced techniques.

Blog 9/15/22

Introduction to Functional Programming in F# – Part 3

Dive into F# data structures and pattern matching. Simplify code and enhance functionality with these powerful features.

Blog 10/1/22

Introduction to Functional Programming in F# – Part 4

Unlock F# collections and pipelines. Manage data efficiently and streamline your functional programming workflow with these powerful tools.

Blog 10/11/22

Introduction to Functional Programming in F# – Part 5

Master F# asynchronous workflows and parallelism. Enhance application performance with advanced functional programming techniques.

Blog 7/12/23

Introduction to Functional Programming in F# – Part 11

Learn type inference and generic functions in F#. Boost efficiency and flexibility in your code with these essential programming concepts.