# Setting Up Universal Storybook (with NativeWind): A Step-by-Step Guide

In this tutorial, we will walk through the process of setting up a Universal Storybook with NativeWind in an Expo project.  
Storybook is a powerful tool for developing UI components in isolation, and NativeWind provides a set of utility classes for building consistent UI designs in React Native applications.

### How Is It Different from Standard Setup?

1. react-native Storybook on web is not feature rich, so we will run storybook-react on web and storybook/react-native on mobile.
    
2. Setting up NativeWind on Storybook is not straight forward. We need to add some loaders and imports to pick up styles.
    

## **A Look at the Versions Used**

* Expo: SDK 51
    
* React Native: 0.74.1
    
* Storybook: \[version\] : react-native: 7.6.18 (latest) web: 8.1.0 (latest)
    
* NativeWind: 4.0.36 (alpha)
    
* Tailwind version: 3.4.3 (latest)
    

## **Setting the Prerequisites**

Before we begin, make sure you have the following installed:

* Node.js (&gt;18) and npm (&gt;9)
    
* expo-cli
    
* storybook-cli
    

## **Step 1: Setup React Native Storybook**

1. **Boot an expo project**: Run this command where you want to make a project in your computer:
    
    ```bash
    npx create-expo-app storybook-nativewind
    ```
    
    I named my project as `storybook-nativewind` . Follow the prompts to set up your project.
    
2. **Reset your project app directory:** Go to the project directory and run this command:
    
    ```bash
    npm run reset-project
    ```
    
    In the latest booted project version of `create-expo-app` , we have a script `reset-project` , which makes the project a fresh project. This will move the current app directory to app-example and just keep the fresh layout and index file.
    
3. **Install Storybook:** Run this command to initiate Storybook setup:
    
    ```bash
    npx storybook@latest init
    ```
    
    Follow the on-screen instructions to complete the setup. It will add a basic story and storybook config to your expo project. It will also install all the necessary dependencies needed.
    
4. **Run Storybook:** Setup main entry file of your expo app to run Storybook.
    
    ```javascript
    // App.tsx or app/index.tsx or _layout.tsx
    
    // import { Text, View } from "react-native";
    // export default function Index() {
    //   return (
    //     <View
    //       style={{
    //         flex: 1,
    //         justifyContent: "center",
    //         alignItems: "center",
    //       }}
    //     >
    //       <Text>Edit app/index.tsx to edit this screen.</Text>
    //     </View>
    //   );
    // }
    export { default } from '../.storybook';
    ```
    
    Adjust export path according to the relative path of `main file` and `.storybook folder` .
    
5. **Setup metro config:** Create metro config file if you do not have it yet. Run this on root of the project:
    
    ```bash
    npx expo customize metro.config.js
    ```
    
    Then set `transformer.unstable_allowRequireContext` to true and add the generate call here.
    
    ```javascript
    //metro.config.js
    
    const path = require('path');
    const { getDefaultConfig } = require('expo/metro-config');
    
    const { generate } = require('@storybook/react-native/scripts/generate');
    
    generate({
      configPath: path.resolve(__dirname, './.storybook'),
    });
    
    /** @type {import('expo/metro-config').MetroConfig} */
    const config = getDefaultConfig(__dirname);
    
    config.transformer.unstable_allowRequireContext = true;
    
    config.resolver.sourceExts.push('mjs');
    
    module.exports = config;
    ```
    
    And your Storybook is set! Now run `npm run start` to run your storybook on any device.
    

## Step 2: Setup Web Storybook

1. Move out `stories` directory from `.storybook` directory to the root of your project.
    
    ```bash
    mv .storybook/stories stories
    ```
    
2. Since you have changed the path of `stories` , you need to fix this in the configuration files.
    
    * `.storybook/main.ts` file
        
    
    ```jsx
    - stories: ["./stories/**/*.stories.?(ts|tsx|js|jsx)"]
    + stories: ["../stories/**/*.stories.?(ts|tsx|js|jsx)"]
    ```
    
    * `.storybook/storybook.require.ts` file
        
    
    ```jsx
     - directory: ".storybook/stories"
     + directory: "./stories"
    ```
    
    ```javascript
    - req: require.context(
          "./stories",
    + req: require.context(
          "../stories",
    ```
    
3. Run the Storybook again to check if the changes done are correct or not.
    
    ```bash
    npm run start
    ```
    
4. Rename `.storybook` to `.ondevice` : We have to setup different configuration files for storybook-web.
    
    ```bash
    mv .storybook .ondevice
    ```
    
5. Fix configuration paths after change the name of folder to `.storybook` to `.ondevice` .
    
    a. Change the path in `metro.config.js` file.
    
    b. Change the path in app entry file.
    
6. Run the storybook again to check if the changes done are working or not.
    
7. create a new `.storybook` folder for web storybook configuration.
    
8. Create a `preview.tsx` and `main.ts` file:
    
    ```javascript
    // preview.tsx
    // copy the content from .ondevice preview.tsx
    
    import type { Preview } from "@storybook/react";
    
    const preview: Preview = {
      parameters: {
        controls: {
          matchers: {
            color: /(background|color)$/i,
            date: /Date$/,
          },
        },
      },
    };
    
    export default preview;
    ```
    
    ```javascript
    // main.ts
    
    const main = {
      stories: [
        "../stories/**/*.stories.mdx",
        "../stories/**/*.stories.?(ts|tsx|js|jsx)",
      ],
      addons: [
        "@storybook/addon-links",
        "@storybook/addon-essentials",
        "@storybook/addon-react-native-web",
      ],
      framework: {
        name: "@storybook/react-webpack5",
        options: {},
      },
      docs: {
        autodocs: true,
      },
    };
    
    export default main;
    ```
    
9. Install the Storybook dev-dependencies for storybook-web.
    
    ```bash
    npm i -D  @storybook/addon-actions@latest @storybook/addon-controls@latest @storybook/addon-essentials@latest @storybook/addon-links@latest @storybook/addon-ondevice-backgrounds@latest @storybook/addon-ondevice-notes@latest @storybook/addon-react-native-web@latest @storybook/addon-styling-webpack@latest @storybook/react@latest @storybook/react-webpack5@latest storybook@latest
    ```
    
10. Run storybook-web: Add scripts in your package.json file to run the Storybook.
    
    ```json
    // package.json
    "storybook:web": "storybook dev -p 6006",
    "build-storybook": "storybook build",
    
    // for stories generation for native
    "storybook-generate": "sb-rn-get-stories --config-path .ondevice",
    ```
    
    ```bash
    // run the storybook web
    npm run storybook:web
    
    // run the storybook native
    npm run start and then choose ios or android
    ```
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1716467094186/0cd14533-0897-45a0-81e4-61c2a62c8486.png align="center")
    

## Step 3: Run Storybook and App Simultaneously with Environment Variable Configuration

1. Create an `app.config.ts` file if not there already and add this to your file.
    
    ```javascript
    extra: {
        storybookEnabled: process.env.STORYBOOK_ENABLED,
      },
    ```
    
2. Go to your main entry file of your application and add the conditionals to run Storybook or app.
    
    ```javascript
    // App.tsx or app/index.tsx
    
    // to import from extra field of app.config.ts
    import Constants from "expo-constants";
    
    function App() {
      return (
        <View>
          <Text>Open up App.tsx to start working on your app!</Text>
        </View>
      );
    }
    
    let AppEntryPoint = App;
    
    if (Constants.expoConfig?.extra?.storybookEnabled === "true") {
      AppEntryPoint = require("./.ondevice").default;
    }
    
    export default AppEntryPoint;
    ```
    
3. Update scripts in the package.json to run Storybook and app.
    
    ```json
        "start": "expo start",
        "android": "expo start --android",
        "ios": "expo start --ios",
        "web": "expo start --web",
        "storybook:web": "storybook dev -p 6006",
        "build-storybook": "storybook build",
        "storybook-generate": "sb-rn-get-stories --config-path .ondevice",
        "storybook": "cross-env STORYBOOK_ENABLED='true' expo start",
        "storybook:ios": "cross-env STORYBOOK_ENABLED='true' expo start --ios",
        "storybook:android": "cross-env STORYBOOK_ENABLED='true' expo start --android"
    ```
    
    Install `cross-env` as a dev dependency if not existing already.
    

## Step 4: Run NativeWind in Storybook

1. Setup NativeWind: Follow the instructions specified in the NativeWind documentation [https://www.nativewind.dev/v4/getting-started/expo-router](https://www.nativewind.dev/v4/getting-started/expo-router)
    
2. Add dev-dependencies:
    
    ```bash
    npm i -D autoprefixer postcss-loader
    ```
    
3. Create `postcss.config.js` file in the root directory of project.
    
    ```javascript
    // postcss.config.js file
    
    module.exports = {
        plugins: {
            tailwindcss: {},
            autoprefixer: {},
        },
    };
    ```
    
4. Add `add-on` in the `main.ts` file of `.storybook` (for web).
    
    ```javascript
    //main.ts file
    
    // other configuration
    addons: [
    ...
    {
      name: "@storybook/addon-styling-webpack",
      options: {
        rules: [
          // Replaces existing CSS rules to support PostCSS
          {
            test: /\.css$/,
            use: [
              "style-loader",
              {
                loader: "css-loader",
                options: { importLoaders: 1 },
              },
              {
                // Gets options from `postcss.config.js` in your project root
                loader: "postcss-loader",
                options: { implementation: require.resolve("postcss") },
              },
            ],
          },
        ],
      },
    },
    ]
    ```
    
5. Add loaders for NativeWind in `webpackFinal` in `main.ts` file of `.storybook` (for web).
    
    ```javascript
     //main.ts file
     
     //other configuration
     webpackFinal: async (config: any) => {
        config.module.rules.push({
          test: /\.(js|jsx|ts|tsx)$/,
          loader: "babel-loader",
          options: {
            presets: [
              ["babel-preset-expo", { jsxImportSource: "nativewind" }],
              "nativewind/babel",
            ],
            plugins: ["react-native-reanimated/plugin"],
          },
        });
    
        return config;
      },
    ```
    
6. Import `global.css` file in the `app/_layout.tsx` file, `.storybook/preview.tsx` file, .`ondevice/preview.tsx` file, `app/index.tsx` file, and `story` file.
    
    ```javascript
    // _layout.tsx file/
    // .storybook/preview.tsx
    // .ondevice/preview.tsx
    // app/index.tsx
    // Button.tsx
    
    import "../global.css"
    ```
    
7. Add paths of stories in your `tailwind.config.ts` file.
    
    ```javascript
    // tailwind.config.js file
    
    content: ["./app/**/*.{js,jsx,ts,tsx}",
        "./stories/**/*.{js,jsx,ts,tsx}",]
    ```
    
8. Testing NativeWind functionality: Add classNames in your components in stories files.
    
    ```javascript
    <Text className="bg-blue-500">
       Hello Nativewind world!
    </Text>
    ```
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1716467153336/c0b858f1-cdd4-406a-a43c-f7b54d71ec87.png align="center")
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1716531833310/aa8e3aed-40ca-49b3-9d68-09b722703e1d.jpeg align="center")

## Summing Up

You have successfully setup your Storybook on Expo Web, Expo Native, React Web with NativeWind setup!

If you get stuck while working through this guide, refer to the [full example on GitHub](https://github.com/makkarMeenu/universal-storybook-nativewind).

### A Common Issue You Might Encounter

1. mdx file support:
    

> Error: Unexpected JSX child: JSXFragment

You cannot write mdx file with `.stories.mdx` extension. Correct way: `Button.mdx` .
