Setting up a Design System in Storybook with React, Styled Components, Tailwind, and Typescript in 2020

Dana Rocha
5 min readMay 12, 2020

--

Design system example

Setup a react app

There are different ways to start a React App depending on your end goal. If your intend to start an Application that will contain some components to be used within this specific project, you could start with:

npx create-react-app my-design-system -template=typescript
cd my-design-system

Since my goal is to create an npm package with the design system to use as a component library for other projects, then I am using the following approach:

mkdir my-design-system 
cd my-design-system
yarn init -y
yarn add -D react @types/react typescript

Note that in this last example, I am installing the packages as development dependencies, since the end-user will already have to react as a dependency in their project in the future.

Setup the tsconfig.json file

// to initialise typescript config file
yarn tsc --init

With the project folder opened in your code editor, uncomment the following:

//tsconfig.json"target": "es6",        // change to es6 instead
"lib": ["dom"],
"jsx": "react", // change it to react instead
"declaration": true,
"declarationMap": true,
"outDir": "./dist", // name a distribution folder
"rootDirs": ["src", "stories"],
// in case your stories will be placed inside src, than just use
"rootDir": "src", instead.
"moduleResolution": "node",
tsconfig.json file should look like this

Now, with the project folder opened in your code editor, add a /src folder to the root of your project.

Tailwind Setup

To use Tailwind together with Styled-Components, we will need to install now the following dependencies:

yarn add -D tailwindcss tailwind.macro@next @types/styled-component spostcss-cli autoprefixer postcss-importyarn add styled-components

Once the packages have been installed, initialise tailwind.config.jswith the command: npx tailwind init

This command will create a tailwind.config.js on the root directory and you could now customize Tailwind properties within the needs of our project.

Create the file tailwind.css in .src/assets/css directory to add the base Tailwind variables according to your project needs, that call the utilities from Tailwinds package.

//.src/assets/css/tailwind.css

@tailwind base;

@tailwind components; // in case you need the components

@tailwind utilities;

On the root directory, add a new file called postcss.config.js with the following code:

// postcss.config.jsmodule.exports = {
plugins: [
require('postcss-import'),
require("tailwindcss"),
require("autoprefixer")
]
};

Tailwind will be processed with postcss, and autoprefixerwill parse the CSS and add vendor prefixes for browser support.

Update your package.json

// package.json"scripts": {
"build:css": "postcss src/assets/css/tailwind.css -o src/assets/css/main.css",
"watch:css": "postcss src/assets/css/tailwind.css -o src/assets/css/main.css -w",
"storybook": "start-storybook -p 8080"
}

Run this command on the terminal so the tailwind file will be generated.

yarn build:css

Place the generated tailwind file in your App component (in case you initiated through create-react-app), or generate an index.tsx in the .src/ folder.

// index.tsx or App.tsximport './assets/css/tailwind.css';

Let's start with Storybook

First, let’s install the dependencies that we need and do some initial storybook config.

yarn add -D @storybook/react react-docgen-typescript-loader @babel/core babel-loader babel-preset-react-app @storybook/addon-info @storybook/addon-centered

Let’s create a new folder at the root of our project called .storybook and create a main.js file inside.

On the main.js file add the following configuration:

//.storybook/main.jsconst path = require('path');module.exports = {
webpackFinal: async config => {
config.module.rules = [
...config.module.rules,
{
test: /\.(ts|tsx)$/,
include: [path.resolve(__dirname, '..')],
use: [
{
loader: require.resolve("babel-loader"),
options: {
presets: [require.resolve("babel-preset-react-app")]
}
},
require.resolve("react-docgen-typescript-loader")
]
}
],
config.resolve.extensions.push('.ts', '.tsx');
return config;
},
};

Now add a second file to the same folder nameconfig.js. In this config file you could add the addons such as addon-info, to your storybook, more references here.

//config.jsimport { configure, addDecorator } from '@storybook/react';
import { withInfo } from '@storybook/addon-info';
import centered from '@storybook/addon-centered/react';
addDecorator(withInfo);
addDecorator(centered);
// automatically import all files ending in *.stories.tsxconfigure(require.context('../stories', true, /\.story\.tsx$/), module);

Create a Story

We would need to have at least one component written in our .src/components folder, and one story added to our ./stories folder, so we could later on check if our storybook is running correctly.

Let's then assume we have a Button component sample in our ./src/components/Button/index.tsx and a Button Style in ./src/components/Button/styles.tslike this:

//./src/components/Button/index.tsximport React, { ButtonHTMLAttributes } from 'react';
import { Container } from './styles';
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
label: string;
}
export const Button: React.FC<ButtonProps> = ({ label, ...rest }) => {
return (
<Container type='button' {...rest}>
{label}
</Container>
);
};

Here we are not using 'export default' because we will want to import the component when published by using: import { Button } from 'my-design-system';

Now, go to your ./src/index.tsxand export the Button component:

export { Button } from './components/Button';

//./src/components/Button/styles.tsimport styled, { css } from 'styled-components';
import tw from 'tailwind.macro';
export const Container = styled.button`
${tw`text-white text-base font-bold text-center bg-indigo-700 rounded-full flex py-4 px-10 focus:outline-none cursor-pointer`}
&:disabled {
${tw`opacity-75 cursor-not-allowed`}
}
&:hover:not(:disabled) {
${tw`g-indigo-800 border-transparent shadow-xs`}
}
&:active:not(:disabled) {
${tw`g-indigo-900 border-transparent shadow-xs`}
}
`;

Note* So, here is where things get interesting. To usetailwind.macro together with styled-components, and extend the powerful capabilities of using Tailwind in the project by adding the 'tw' tagged template literal, we need to have a few extras steps together with typescript.

Now, if you reached until here, you will notice that typescript will give an error on our ./src/components/Button/styles.ts since this plugin does not have @types support yet, so to turn this around we have to declare the module manually. In the root directory, create a folder namedtypes and inside this folder, create another folder named tailwind.macro. Inside this folder create a file named index.d.ts with the following code:

declare module 'tailwind.macro' {
export default function tw(string: TemplateStringsArray): string;
}

This should now clear out the previous error in our ./src/components/Button/styles.ts file.

*Note that in the main.js file in our .storybook folder, we already have a babel-loader instead of the ts-loader, because oftailwind.macro.

Create astories folder at the root directory.

//.stories/Button.story.tsximport React from "react";
import { storiesOf } from "@storybook/react";
import { Button } from "../src";storiesOf("Button", module).add("Default", () => <Button label="Continue" />);

Running storybook

And, there you go! Awesome.

Now if we run yarn storybook we should get something like this.

BAM! Great job!

Thanks for reading.

If you'd like, this same boilerplate is available on Github: https://github.com/danarocha-br/storybook-react-typescript-tailwind-styled, check it out!

--

--

Dana Rocha

✏️ Digital Product Designer • 🚀 Front-end Developer Enthusiast • 🇧🇷 / 🇪🇪