close

React Native Web

Storybook for React Native Web & Rsbuild enables you to develop and document React Native components that run on the web using react-native-web.

This framework extends storybook-react-rsbuild to provide full React Native Web compatibility, including alias resolution, web-specific extensions, and transpilation of React Native packages.

Requirements

PackageVersion
react≥ 16.8
react-native-web≥ 0.19.0
@rsbuild/core≥ 1.5.0
@rsbuild/plugin-react≥ 1.0.0
storybook≥ 10.1.0

Getting started

Installation

Install the framework package and its peer dependencies:

npm
yarn
pnpm
bun
deno
npm install storybook-react-native-web-rsbuild react-native-web @rsbuild/plugin-react -D

Configure rsbuild.config.ts

Create or update your Rsbuild configuration to include the React plugin:

import { defineConfig } from '@rsbuild/core'
import { pluginReact } from '@rsbuild/plugin-react'

export default defineConfig({
  plugins: [pluginReact()],
})

Configure .storybook/main.ts

import type { StorybookConfig } from 'storybook-react-native-web-rsbuild'

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  framework: {
    name: 'storybook-react-native-web-rsbuild',
    options: {},
  },
}

export default config

Features

The framework automatically handles:

  • Alias resolution: react-nativereact-native-web
  • Web extensions: Prioritizes .web.tsx, .web.ts, .web.js files
  • Global definitions: Sets up __DEV__, EXPO_OS, and other React Native globals
  • Package transpilation: Transpiles react-native, @react-native, expo, and @expo packages
  • React docgen: Full support for props documentation via react-docgen

Framework Options

interface FrameworkOptions {
  /**
   * Additional node_modules that need to be transpiled.
   * Packages starting with `react-native`, `@react-native`, `expo`, and `@expo`
   * are included by default.
   */
  modulesToTranspile?: string[]

  /**
   * Options passed to the underlying rsbuild-plugin-react-native-web plugin.
   */
  pluginOptions?: PluginReactNativeWebOptions
}

interface PluginReactNativeWebOptions {
  /**
   * Additional node_modules that need to be transpiled.
   * @example ['my-react-native-library']
   */
  modulesToTranspile?: string[]

  /**
   * The JSX runtime to use.
   * @default 'automatic'
   */
  jsxRuntime?: 'automatic' | 'classic'

  /**
   * The source for JSX imports when using the automatic runtime.
   * @default 'react'
   * @example 'nativewind' for NativeWind v4+
   */
  jsxImportSource?: string

  /**
   * Modules that should not be tree-shaken.
   * @default ['react-native-css-interop', 'expo-modules-core']
   */
  noTreeshakeModules?: string[]
}

Example: Transpiling additional packages

Some React Native libraries ship untranspiled code. Add them to modulesToTranspile:

const config: StorybookConfig = {
  framework: {
    name: 'storybook-react-native-web-rsbuild',
    options: {
      modulesToTranspile: [
        'react-native-reanimated',
        '@react-navigation/native',
      ],
    },
  },
}

Writing Stories

Write stories using standard React Native components:

// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from './Button'

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
}

export default meta
type Story = StoryObj<typeof Button>

export const Primary: Story = {
  args: {
    label: 'Press me',
    variant: 'primary',
  },
}
// Button.tsx
import { Text, TouchableOpacity, StyleSheet } from 'react-native'

export function Button({ label, onPress, variant = 'primary' }) {
  return (
    <TouchableOpacity onPress={onPress} style={[styles.button, styles[variant]]}>
      <Text style={styles.text}>{label}</Text>
    </TouchableOpacity>
  )
}

const styles = StyleSheet.create({
  button: {
    paddingVertical: 12,
    paddingHorizontal: 20,
    borderRadius: 8,
  },
  primary: {
    backgroundColor: '#007AFF',
  },
  text: {
    color: 'white',
    fontWeight: 'bold',
  },
})

NativeWind Support

This framework supports NativeWind for using Tailwind CSS with React Native components.

Installation

npm
yarn
pnpm
bun
deno
npm install nativewind react-native-css-interop react-native-safe-area-context tailwindcss postcss autoprefixer -D
Tip

Installing react-native-safe-area-context is recommended to avoid build warnings from react-native-css-interop.

Configuration

  1. Configure Tailwind CSS - Create tailwind.config.js:
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./src/**/*.{js,jsx,ts,tsx}'],
  presets: [require('nativewind/preset')],
  theme: {
    extend: {},
  },
  plugins: [],
}
  1. Create global CSS - Create src/global.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
  1. Import CSS in preview - Update .storybook/preview.tsx:
import '../src/global.css'

const preview = {
  // your preview config
}

export default preview
  1. Configure JSX import source - Update .storybook/main.ts:
const config: StorybookConfig = {
  framework: {
    name: 'storybook-react-native-web-rsbuild',
    options: {
      pluginOptions: {
        jsxImportSource: 'nativewind',
      },
    },
  },
}

Usage

import { View, Text } from 'react-native'

export function Card({ title, children }) {
  return (
    <View className="p-4 bg-white rounded-lg shadow-md">
      <Text className="text-xl font-bold text-gray-800">{title}</Text>
      {children}
    </View>
  )
}

React Native Reanimated

This framework provides built-in support for React Native Reanimated on web.

Installation

npm
yarn
pnpm
bun
deno
npm install react-native-reanimated react-native-worklets

What the framework handles

The framework automatically:

  • Defines _WORKLET and _frameTimestamp globals required by Reanimated
  • Transforms Reanimated's webUtils for ESM compatibility
  • Handles module resolution in pnpm/monorepo environments

Usage

import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'

export function FadeInView({ children }) {
  return (
    <Animated.View entering={FadeIn.duration(500)} exiting={FadeOut}>
      {children}
    </Animated.View>
  )
}

Expo Support

This framework works with Expo projects. Ensure your metro.config.js or bundler configuration is not conflicting with Rsbuild.

For Expo-specific globals, the plugin automatically defines:

  • EXPO_OS'web'
  • process.env.EXPO_OS'web'

Next Steps

Troubleshooting

"React is not defined" error

Make sure you have @rsbuild/plugin-react installed and configured in rsbuild.config.ts. This plugin enables the automatic JSX runtime.

Module not found errors for React Native packages

Add the problematic package to modulesToTranspile in your framework options.

TypeScript errors for react-native imports

Install @types/react-native as a dev dependency, or create a react-native.d.ts file:

declare module 'react-native' {
  export * from 'react-native-web'
}

Flow syntax errors in React Native packages

Some older React Native packages may contain Flow type annotations that are not stripped before publishing. If you encounter syntax errors related to Flow types (e.g., Unexpected token : in type annotations), you can use Babel to strip Flow types.

  1. Install the required packages:
pnpm add -D @babel/core babel-loader @babel/preset-flow @babel/preset-react
  1. Add a custom Rsbuild configuration in .storybook/main.ts:
const config: StorybookConfig = {
  framework: {
    name: 'storybook-react-native-web-rsbuild',
    options: {},
  },
  rsbuildFinal: async (config) => {
    config.tools ??= {}
    config.tools.bundlerChain = (chain) => {
      // Add babel-loader for packages with Flow syntax
      chain.module
        .rule('flow')
        .test(/\.jsx?$/)
        .include.add(/node_modules\/(react-native|@react-native)/)
        .end()
        .use('babel')
        .loader('babel-loader')
        .options({
          presets: ['@babel/preset-flow', '@babel/preset-react'],
        })
    }
    return config
  },
}

This configuration uses Babel to process React Native packages and strip Flow type annotations before they are handled by SWC.