storybook
Storybook is the industry standard workshop for building, documenting, and testing UI components in isolation
A new docs site is being built for Storybook for React Native, you can find it at This readme is for v10, for v9 docs see the v9.1 docs. With Storybook for React Native you can design and develop individual React Native components without running your app.
> /plugin marketplace add storybookjs/react-native> /plugin install react-native-storybook@react-native-storybook
Repo: storybookjs/react-native
What's inside
A new docs site is being built for Storybook for React Native, you can find it at https://storybookjs.github.io/react-native/docs/intro/.
[!IMPORTANT] This readme is for v10, for v9 docs see the v9.1 docs.
With Storybook for React Native you can design and develop individual React Native components without running your app.
If you are migrating from 9 to 10 you can find the migration guide here
For more information about storybook visit: storybook.js.org
[!NOTE] Make sure you align your storybook dependencies to the same major version or you will see broken behaviour.
There is some project boilerplate with @storybook/react-native and @storybook/addon-react-native-web both already configured with a simple example.
For Expo you can use this template with the following command
# With NPM
npx create-expo-app --template expo-template-storybook AwesomeStorybook
For React Native CLI you can use this template
npx @react-native-community/cli init MyApp --template react-native-template-storybook
Run init to setup your project with all the dependencies and configuration files:
npm create storybook@latest
Then wrap your bundler config with the withStorybook function. It auto-detects Metro vs Re.Pack and handles everything โ entry-point swapping, story generation, and optional WebSocket setup.
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const { withStorybook } = require('@storybook/react-native/withStorybook');
const config = getDefaultConfig(__dirname);
module.exports = withStorybook(config);
No changes to App.tsx are needed. Set STORYBOOK_ENABLED=true and run:
STORYBOOK_ENABLED=true expo start
The wrapper automatically swaps your app's entry point with Storybook's entry point. When the variable is not set, your app runs normally with zero Storybook code in the bundle.
If you want to add everything yourself check out the manual setup guide.
Make sure you have react-native-reanimated in your project and the plugin setup in your babel config.
// babel.config.js
plugins: ['react-native-reanimated/plugin'],
For projects using Re.Pack (Rspack/Webpack) instead of Metro, see the full Re.Pack Setup guide. You can also reference the RepackStorybookStarter project.
For Expo Router projects, you can either use entry-point swapping (recommended) or create a dedicated Storybook route.
See the full Expo Router Setup guide for details.
In Storybook we use a syntax called CSF that looks like this:
import type { Meta, StoryObj } from '@storybook/react-native';
import { MyButton } from './Button';
const meta = {
component: MyButton,
} satisfies Meta<typeof MyButton>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Basic: Story = {
args: {
text: 'Hello World',
color: 'purple',
},
};
You should configure the path to your story files in the main.ts config file from the .rnstorybook folder.
// .rnstorybook/main.ts
import type { StorybookConfig } from '@storybook/react-native';
const main: StorybookConfig = {
stories: ['../components/**/*.stories.?(ts|tsx|js|jsx)'],
deviceAddons: ['@storybook/addon-ondevice-controls', '@storybook/addon-ondevice-actions'],
};
export default main;
For stories you can add decorators and parameters on the default export or on a specific story.
import type { Meta } from '@storybook/react';
import { Button } from './Button';
const meta = {
title: 'Button',
component: Button,
decorators: [
(Story) => (
<View style={{ alignItems: 'center', justifyContent: 'center', flex: 1 }}>
<Story />
</View>
),
],
parameters: {
backgrounds: {
values: [
{ name: 'red', value: '#f00' },
{ name: 'green', value: '#0f0' },
{ name: 'blue', value: '#00f' },
],
},
},
} satisfies Meta<typeof Button>;
export default meta;
For global decorators and parameters, you can add them to preview.tsx inside your .rnstorybook folder.
// .rnstorybook/preview.tsx
import type { Preview } from '@storybook/react-native';
import { withBackgrounds } from '@storybook/addon-ondevice-backgrounds';
const preview: Preview = {
decorators: [
withBackgrounds,
(Story) => (
<View style={{ flex: 1, color: 'blue' }}>
<Story />
</View>
),
],
parameters: {
backgrounds: {
default: 'plain',
values: [
{ name: 'plain', value: 'white' },
{ name: 'warm', value: 'hotpink' },
{ name: 'cool', value: 'deepskyblue' },
],
},
},
};
export default preview;
The cli will install some basic addons for you such as controls and actions. Ondevice addons are addons that can render with the device ui that you see on the phone.
Currently, the addons available are:
@storybook/addon-ondevice-controls: adjust your components props in realtime@storybook/addon-ondevice-actions: mock onPress calls with actions that will log information in the actions tab@storybook/addon-ondevice-notes: Add some Markdown to your stories to help document their usage@storybook/addon-ondevice-backgrounds: change the background of storybook to compare the look of your component against different backgroundsInstall each one you want to use and add them to the deviceAddons list in your main.ts:
// .rnstorybook/main.ts
import type { StorybookConfig } from '@storybook/react-native';
const main: StorybookConfig = {
// ... rest of config
deviceAddons: [
'@storybook/addon-ondevice-notes',
'@storybook/addon-ondevice-controls',
'@storybook/addon-ondevice-backgrounds',
'@storybook/addon-ondevice-actions',
],
};
export default main;
[!NOTE]
deviceAddonsensures on-device addons are only loaded at runtime on the device, avoiding errors during server-side operations. For backwards compatibility, listing them inaddonsstill works.
For details of each ondevice addon you can see the readme:
Starting with v10.4, entry-point swapping is the default setup. Your existing in-app integration setup continues to work and is fully supported, but entry-point swapping is the recommended approach for new projects.
When using the bundler-agnostic withStorybook wrapper, set STORYBOOK_ENABLED=true to run Storybook. The wrapper swaps your app's entry point with Storybook's entry point automatically. When the variable is not set, your app runs normally with zero Storybook code in the bundle.
{
"scripts": {
"storybook": "STORYBOOK_ENABLED=true expo start",
"storybook:ios": "STORYBOOK_ENABLED=true expo start --ios"
}
}
Create a dedicated route for Storybook:
// app/storybook.tsx
export { default } from '../.rnstorybook';
Then navigate to /storybook in your app to view stories.
You can also import Storybook directly in your App.tsx. This approach continues to work and is fully supported:
import StorybookUI from './.rnstorybook';
import { MyApp } from './MyApp';
const isStorybook = process.env.EXPO_PUBLIC_STORYBOOK_ENABLED === 'true';
export default function App() {
return isStorybook ? <StorybookUI /> : <MyApp />;
}
withStorybook is a bundler-agnostic wrapper that configures your project for Storybook. It auto-detects whether you're using Metro or Re.Pack and handles entry-point swapping, story generation, and WebSocket setup.
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const { withStorybook } = require('@storybook/react-native/withStorybook');
const defaultConfig = getDefaultConfig(__dirname);
module.exports = withStorybook(defaultConfig);
When STORYBOOK_ENABLED=true is set, the wrapper activates. When it's not set, the wrapper is a no-op and your app runs normally.
Options can be passed as a second argument. Most settings can also be controlled via environment variables (see Environment Variables).
Type: string, default: path.resolve(process.cwd(), './.rnstorybook')
The location of your Storybook configuration directory, which includes main.ts and other project-related files.
Type: boolean, default: false
Generates the .rnstorybook/storybook.requires file in JavaScript instead of TypeScript.
Type: boolean, default: true
Whether to include doc tools in the storybook.requires file. Doc tools provide additional documentation features and work with babel-plugin-react-docgen-typescript.
Type: boolean, default: false
Whether to use lite mode for Storybook. In lite mode, the default Storybook UI is mocked out so you don't need to install all its dependencies like react-native-reanimated. This is useful for reducing bundle size and dependencies. Use this when using @storybook/react-native-ui-lite instead of @storybook/react-native-ui. Note: STORYBOOK_DISABLE_UI=true is equivalent to onDeviceUI: false, not liteMode: true.
Type: boolean, default: false
Enables an experimental MCP (Model Context Protocol) server for AI tooling to query Storybook documentation and component/story metadata.
The MCP server is available at the /mcp endpoint on the Storybook channel server. Configure your MCP client via its settings UI, or use:
npx mcp-add --type http --url "http://localhost:7007/mcp" --scope project
Type: 'auto' | { host?: string, port?: number, secured?: boolean, key?: string | Buffer, cert?: string | Buffer, ca?: string | Buffer | Array<string | Buffer>, passphrase?: string }, default: undefined
If specified, create a WebSocket server on startup. This allows you to sync up multiple devices to show the same story and arg values connected to the story in the UI.
Use 'auto' to automatically detect your LAN IP and inject host/port into the generated storybook.requires file. WebSocket settings can also be overridden via STORYBOOK_WS_HOST, STORYBOOK_WS_PORT, and STORYBOOK_WS_SECURED environment variables.
Note: A Metro-specific
withStorybookis also available at@storybook/react-native/metro/withStorybookfor advanced Metro configuration. See the Metro Configuration docs for details.
You can pass these parameters to getStorybookUI call in your storybook entry point:
{
initialSelection?: string | Object;
storage?: {
getItem: (key: string) => Promise<string | null>;
setItem: (key: string, value: string) => Promise<void>;
};
onDeviceUI?: boolean;
shouldPersistSelection?: boolean;
theme: Partial<Theme>;
}
Note: WebSocket options (
enableWebsockets,host,port,secured) are auto-injected when using the bundler-agnosticwithStorybookwrapper. You only need to set them manually if you're using the Metro-specific wrapper or a custom setup.
Feature flags let you opt into new functionality without breaking existing behavior. In the next major version, the behavior behind these flags will become the default and the flags will no longer be needed.
Add them to the features object in main.ts:
// .rnstorybook/main.ts
import type { StorybookConfig } from '@storybook/react-native';
const main: StorybookConfig = {
stories: ['../components/**/*.stories.?(ts|tsx|js|jsx)'],
deviceAddons: ['@storybook/addon-ondevice-controls'],
features: {
ondeviceBackgrounds: true,
},
};
export default main;
| Flag | Description |
|---|---|
ondeviceBackgrounds | New backgrounds API with globals-based configuration, full-screen support, and no extra package needed. Available from v10.3. |
For full documentation including configuration examples, see the Feature Flags guide.
Storybook provides testing utilities that allow you to reuse your stories in external test environments, such as Jest. This way you can write unit tests easier and reuse the setup which is already done in Storybook, but in your unit tests. You can find more information about it in the portable stories section.
We welcome contributions to Storybook!
Looking for a first issue to tackle?
Here are some example projects to help you get started
This repo includes agent skills for setting up and working with Storybook for React Native.
storiesOf stories to CSF during the 6.5.x to 7.6.x migrationnpx skills add storybookjs/react-native
This works with any agent harness that supports skills (Claude Code, Cursor, Windsurf, etc.).
.changeset/
config.json
README.md
.claude/
.claude-plugin/
marketplace.json
plugin.json
commands/
pr-description.md
launch.json
.editorconfig
.gitattributes
.github/
CODEOWNERS
FUNDING.yml
ISSUE_TEMPLATE/
ISSUE_TEMPLATE.md
bug_report.md
feature_request.md
PULL_REQUEST_TEMPLATE.md
workflows/
docs.yml
publish.yml
test.yml
.gitignore
.mcp.json
.nvmrc
.prettierignore
.prettierrc
.vscode/
launch.json
settings.json
.zed/
settings.json
AGENTS.md
CHANGELOG.md
CLAUDE.md
CODE_OF_CONDUCT.md
CONTRIBUTING.md
docs/
.gitignore
blog/
2025-03-23-first-blog-post.mdx
2026-02-18-storybook-v10.mdx
2026-02-18-websockets-and-tooling.mdx
authors.yml
tags.yml
CHANGELOG.md
docs/
intro/
addons/
controls.md
index.md
configuration/
backgrounds.md
cli-configuration.md
environment-variables.md
feature-flags.md
index.md
mcp-configuration.md
metro-configuration.md
storybook-ui-configuration.md
websocket-configuration.md
development-workflows.md
getting-started/
expo-router.md
index.md
manual-setup.md
migrating-to-entry-point-swapping.md
repack.md
index.md
sharing-storybook.md
testing.md
writing-stories.md
docusaurus.config.ts
package.json
README.md
sidebars.ts
src/
components/
HomepageFeatures/
index.tsx
styles.module.css
css/
custom.css
pages/
index.module.css
index.tsx
static/
.nojekyll
img/
favicon.ico
logo.svg
social-card.jpg
robots.txt
tsconfig.json
e2e/
agent-device/
storybook-addons/
expo-example-full-ui.yaml
expo-new-wrapper-lite-ui.yaml
expo-router-lite-ui.yaml
README.md
repack-full-ui.yaml
select-story-sync.js
eslint.config.js
examples/
expo-example/
.gitignore
.maestro/
output/
screenshots/
ActionExample-Actions---Basic.png
BackgroundExample-BackgroundCsf---Basic.png
BackgroundExample-ThemedCard---Dark-Locked.png
BackgroundExample-ThemedCard---Default.png
ControlExamples-Array---Basic.png
ControlExamples-Boolean---Basic.png
ControlExamples-Boolean---On.png
ControlExamples-Color---Color-Example.png
ControlExamples-ControlExample---Example.png
ControlExamples-Date---Basic.png
ControlExamples-Number---Basic.png
ControlExamples-Number---Range.png
ControlExamples-Object---Basic.png
ControlExamples-Radio---Basic.png
ControlExamples-Reproductions-SelectWithNumber---Basic.png
ControlExamples-Select---Basic.png
ControlExamples-Select---With-Labels.png
ControlExamples-Select---With-Mapping.png
ControlExamples-Text---Basic.png
ControlExamples-WebCompatibility---Defined.png
ControlExamples-WebCompatibility---Undefined.png
DeepControls---Basic.png
HiddenControls---Basic.png
InputExample-TextInput---Basic.png
InteractionExample---Scrolling.png
InteractionExample---Static.png
InteractionExample---Touchable.png
LoginDocsExample-Button---Disabled.png
LoginDocsExample-Button---Loading.png
LoginDocsExample-Button---Long-Title.png
LoginDocsExample-Button---Primary.png
LoginDocsExample-Button---Secondary-Disabled.png
LoginDocsExample-Button---Secondary-Loading.png
LoginDocsExample-Button---Secondary.png
LoginDocsExample-LoginForm---Default.png
LoginDocsExample-LoginForm---Email-Error-Only.png
LoginDocsExample-LoginForm---Loading.png
LoginDocsExample-LoginForm---Long-Errors.png
LoginDocsExample-LoginForm---Password-Error-Only.png
LoginDocsExample-LoginForm---With-Errors.png
LoginDocsExample-TextInput---Default.png
LoginDocsExample-TextInput---Empty-State.png
LoginDocsExample-TextInput---Filled.png
LoginDocsExample-TextInput---Long-Error-Message.png
LoginDocsExample-TextInput---Long-Label.png
LoginDocsExample-TextInput---Password.png
LoginDocsExample-TextInput---Playground.png
LoginDocsExample-TextInput---With-Error.png
LoginDocsExample-TextInput---With-Label.png
NestingExample-ChatMessage---Message-First.png
NestingExample-ChatMessage---Message-Second.png
NestingExample-Message-bubble---First.png
NestingExample-Message-bubble---Second-Story.png
NestingExample-Message-bubble-a-very-long-name-for-a-title-that-just-keeps-going-and-going---First.png
NestingExample-Message-bubble-a-very-long-name-for-a-title-that-just-keeps-going-and-going---Second-Story.png
NestingExample-Message-Reactions---Message-One.png
NestingExample-Message-Reactions---Message-Two.png
NestingExample-MessageInput---Basic.png
NotesExample---Notes-Example.png
react-native-ui-src-Layout---Basic.png
react-native-ui-src-Layout---Overflow-Addons-Example.png
react-native-ui-src-Layout---Overflow-Sidebar-Example.png
react-native-ui-UI-SearchResults---Default.png
react-native-ui-UI-Sidebar-Explorer---Simple.png
react-native-ui-UI-Sidebar-Sidebar---Empty.png
react-native-ui-UI-Sidebar-Sidebar---Index-Error.png
react-native-ui-UI-Sidebar-Sidebar---Loading-With-Ref-Error.png
react-native-ui-UI-Sidebar-Sidebar---Loading-With-Refs.png
react-native-ui-UI-Sidebar-Sidebar---Loading.png
react-native-ui-UI-Sidebar-Sidebar---Scrolled.png
react-native-ui-UI-Sidebar-Sidebar---Searching.png
react-native-ui-UI-Sidebar-Sidebar---Simple.png
react-native-ui-UI-Sidebar-Sidebar---Statuses-Collapsed.png
react-native-ui-UI-Sidebar-Sidebar---Statuses-Open.png
react-native-ui-UI-Sidebar-Sidebar---With-Ref-Empty.png
react-native-ui-UI-Sidebar-Sidebar---With-Refs.png
react-native-ui-UI-Sidebar-Tree---Dark.png
react-native-ui-UI-Sidebar-Tree---Full.png
react-native-ui-UI-Sidebar-Tree---Single-Story-Components.png
react-native-ui-UI-Sidebar-Tree---Story-with-a-storyName.png
react-native-ui-UI-Sidebar-TreeNode---Expandable-Long-Name.png
react-native-ui-UI-Sidebar-TreeNode---Expandable.png
react-native-ui-UI-Sidebar-TreeNode---Nested.png
react-native-ui-UI-Sidebar-TreeNode---Selection-With-Long-Name.png
react-native-ui-UI-Sidebar-TreeNode---Selection.png
react-native-ui-UI-Sidebar-TreeNode---Types.png
react-native-ui-UI-StorybookLogo---Image-Element-Logo.png
react-native-ui-UI-StorybookLogo---Image-Logo.png
react-native-ui-UI-StorybookLogo---Image-Source-Logo.png
react-native-ui-UI-StorybookLogo---Image-Url-Logo.png
react-native-ui-UI-StorybookLogo---Title-Logo.png
SafeAreaExample-SafeAreaInside---Basic.png
SafeAreaExample-SafeAreaInside---List-Basic.png
SafeAreaExample-SafeAreaOutside---Basic.png
SafeAreaExample-SafeAreaOutside---List-Basic.png
SafeAreaExample-UsableArea---No-Safe-Area.png
SafeAreaExample-UsableArea---Safe-Area.png
StoryName---CSF-2-Example.png
StoryName---csf2-with-name.png
StoryName---With-Name.png
StoryName---With-No-Name.png
StoryName---With-Story-Name.png
TestCase---Basic.png
TestCase2---Basic.png
UserProfileCard---Default.png
UserProfileCard---Following.png
UserProfileCard---Long-Content.png
UserProfileCard---Minimal-Card.png
UserProfileCard---Online-User.png
UserProfileCard---Popular-User.png
UserProfileCard---Verified-User.png
UserProfileCard---With-Avatar.png
storybook-screenshots.capture.yaml
storybook-screenshots.select-story-sync.js
storybook-screenshots.yaml
.rnstorybook/
index.tsx
local-addon-example/
preview.js
register.js
main.ts
preview.tsx
storybook.requires.ts
.storybook/
main.ts
preview.tsx
app.json
App.tsx
assets/
icon.png
storybook.icon/
Assets/
icon-storybook-default.svg
icon.json
babel.config.js
components/
ActionExample/
Actions.stories.tsx
Actions.test.tsx
Actions.tsx
AddonPanelDisable/
AddonPanelDisable.stories.tsx
BackgroundExample/
BackgroundCsf.stories.tsx
BackgroundCsf.test.tsx
ThemedCard.stories.tsx
ThemedCard.tsx
ControlExamples/
Array/
Array.stories.tsx
Array.test.tsx
Array.tsx
Boolean/
Boolean.stories.tsx
Boolean.test.tsx
Boolean.tsx
Color/
Color.stories.tsx
Color.test.tsx
Color.tsx
ControlExample/
ControlExample.stories.tsx
ControlExample.tsx
Date/
Date.stories.tsx
Date.test.tsx
Date.tsx
Number/
Number.stories.tsx
Number.test.tsx
Number.tsx
Object/
Object.stories.tsx
Object.test.tsx
Object.tsx
Radio/
Radio.stories.tsx
Radio.test.tsx
Radio.tsx
Reproductions/
SelectWithNumber.stories.tsx
SelectWithNumber.tsx
UseArgsLatency.stories.tsx
Select/
Select.stories.tsx
Select.test.tsx
Select.tsx
Text/
Text.stories.tsx
Text.test.tsx
Text.tsx
WebCompatibility/
WebCompatibility.stories.tsx
DeepControls/
DeepControls.stories.tsx
HiddenControls/
HiddenControls.stories.tsx
InputExample/
TextInput.stories.tsx
TextInput.test.tsx
TextInput.tsx
InteractionExample/
InteractionExample.stories.tsx
LoginDocsExample/
Button/
Button.stories.tsx
Button.tsx
LoginForm/
LoginForm.stories.tsx
LoginForm.tsx
TextInput/
TextInput.stories.tsx
TextInput.tsx
NestingExample/
ChatComponents.tsx
ChatMessage.stories.tsx
ChatMessageBubble.stories.tsx
ChatMessageBubbleAgain.stories.tsx
ChatMessageMessageInput.stories.tsx
ChatMessageReactions.stories.tsx
NotesExample/
NotesExample.stories.tsx
SafeAreaExample/
AButton.tsx
SafeAreaInside.stories.tsx
SafeAreaOutside.stories.tsx
UsableArea.stories.tsx
StoryName/
StoryName.stories.tsx
UserProfileCard/
UserProfileCard.stories.tsx
UserProfileCard.tsx
eas.json
index.js
jest-sucrase-transformer.js
jest.config.js
metro.config.js
other_components/
TestCase/
TestCase.stories.tsx
TestCase2/
TestCase2.stories.tsx
package.json
README.md
scripts/
build-index.ts
generate-dev-cert.sh
generatePerfTests.ts
start-channel-server.ts
test-ws.ts
setup-jest.ts
tsconfig.json
expo-new-wrapper-example/
.expo/
devices.json
prebuild/
cached-packages.json
README.md
.rnstorybook/
index.tsx
main.ts
preview.tsx
storybook.requires.ts
app.json
App.tsx
babel.config.js
components/
Button/
Button.stories.tsx
Button.tsx
HelloText/
HelloText.stories.tsx
HelloText.tsx
index.js
metro.config.js
package.json
README.md
tsconfig.json
expo-router-example/
.gitignore
.rnstorybook/
index.tsx
main.ts
preview.tsx
storybook.requires.ts
.vscode/
extensions.json
settings.json
... 475 moreStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation
FAQ
react-native-storybook is a Claude Code plugin with 3 hand-picked skills for development work, indexed on Flowy. Install it with the command on its page. It includes setup-react-native-storybook, upgrading-react-native-storybook, writing-react-native-storybook-stories. Its skills do not fire on their own yet. Request auto-invocation to have Flowy route them as you prompt. Free and open source.