Addons extend Storybook’s functionality, providing features like interactive controls, action logging, background switching, and documentation. Storybook for React Native includes specialized on-device addons that render directly on your mobile device.
On-Device Addons
On-device addons are specifically designed for React Native, rendering their UI directly on your phone or emulator. They provide a native mobile experience for interacting with your stories.
Available Addons
The official on-device addon suite includes:
Controls Dynamically edit component props in real-time
Actions Log and inspect component interactions
Backgrounds Change story backgrounds to test contrast
Notes Add Markdown documentation to stories
Installation and Configuration
The CLI typically installs basic addons for you. To manually add addons:
Install Addons
pnpm add -D @storybook/addon-ondevice-controls
pnpm add -D @storybook/addon-ondevice-actions
pnpm add -D @storybook/addon-ondevice-backgrounds
pnpm add -D @storybook/addon-ondevice-notes
Register Addons
Add them to your .rnstorybook/main.ts configuration:
// .rnstorybook/main.ts
import type { StorybookConfig } from '@storybook/react-native' ;
const main : StorybookConfig = {
stories: [
'../components/**/*.stories.?(ts|tsx|js|jsx)' ,
],
addons: [
'@storybook/addon-ondevice-notes' ,
'@storybook/addon-ondevice-controls' ,
'@storybook/addon-ondevice-backgrounds' ,
'@storybook/addon-ondevice-actions' ,
],
framework: '@storybook/react-native' ,
};
export default main ;
The order of addons in the addons array determines the order of tabs in the on-device UI.
Controls Addon
The Controls addon lets you dynamically edit component props using interactive controls. It automatically generates controls based on your component’s props and argTypes.
Basic Usage
import { Meta , StoryObj } from '@storybook/react-native' ;
import { ControlExample } from './ControlExample' ;
const meta = {
component: ControlExample ,
args: {
name: 'Storyteller' ,
age: 70 ,
fruit: 'apple' ,
dollars: 12.5 ,
backgroundColor: '#eaeaea' ,
items: [ 'Laptop' , 'Book' , 'Whiskey' ],
customStyles: {
borderWidth: 3 ,
borderColor: '#000' ,
padding: 10 ,
},
nice: true ,
birthday: new Date ( 2017 , 0 , 20 ),
},
} satisfies Meta < typeof ControlExample >;
export default meta ;
Control Types
Customize controls with argTypes:
Range Slider
Select Dropdown
Radio Buttons
Color Picker
Date Picker
Array/Object
argTypes : {
age : {
step : 5 ,
min : 0 ,
max : 90 ,
range : true ,
},
}
argTypes : {
fruit : {
options : [ 'apple' , 'banana' , 'cherry' ] as const ,
control : {
type : 'select' ,
labels : {
apple : 'Apple' ,
banana : 'Banana' ,
cherry : 'Cherry' ,
} as const ,
},
},
}
argTypes : {
otherFruit : {
options : [ 'kiwi' , 'guava' , 'watermelon' ] as const ,
control : {
type : 'radio' ,
labels : {
kiwi : 'Kiwi' ,
guava : 'Guava' ,
watermelon : 'Watermelon' ,
} as const ,
},
},
}
argTypes : {
backgroundColor : {
control : { type : 'color' },
},
}
argTypes : {
birthday : {
control : { type : 'date' },
},
}
argTypes : {
items : {
control : {
type : 'array' ,
},
},
customStyles : {
control : { type : 'object' },
},
}
Auto-Detection
Configure automatic control detection in your preview:
// .rnstorybook/preview.tsx
import type { Preview } from '@storybook/react-native' ;
const preview : Preview = {
parameters: {
controls: {
matchers: {
color: / ( background | color ) $ / i ,
date: /Date $ / ,
},
},
},
};
export default preview ;
Actions Addon
The Actions addon logs component interactions like button presses, providing visibility into event handlers.
Implementation
The Actions addon is implemented in packages/ondevice-actions/src/index.tsx:
import { ADDON_ID , PANEL_ID , PARAM_KEY } from 'storybook/actions' ;
import { addons , types } from 'storybook/manager-api' ;
import ActionLogger from './containers/ActionLogger' ;
export function register () {
addons . register ( ADDON_ID , ( _api ) => {
addons . add ( PANEL_ID , {
type: types . PANEL ,
title: 'Actions' ,
render : ({ active }) => < ActionLogger active = { active } /> ,
paramKey: PARAM_KEY ,
});
});
}
Using Actions
Use the fn() function to create trackable mock functions:
import type { Meta , StoryObj } from '@storybook/react-native' ;
import { ActionButton } from './Actions' ;
import { fn } from 'storybook/test' ;
const meta = {
component: ActionButton ,
} satisfies Meta < typeof ActionButton >;
export default meta ;
type Story = StoryObj < typeof meta >;
export const Basic : Story = {
args: {
text: 'Press me!' ,
onPress: fn (), // Logged to Actions panel
},
};
Auto-Logging
Automatically log all event handlers matching a pattern:
// .rnstorybook/preview.tsx
import type { Preview } from '@storybook/react-native' ;
const preview : Preview = {
parameters: {
actions: {
argTypesRegex: '^on[A-Z].*' // Auto-log onPress, onChange, etc.
},
},
};
export default preview ;
Combine Actions with Controls to test component behavior interactively. Change props with Controls and see the resulting actions logged.
Backgrounds Addon
The Backgrounds addon lets you change the background color of your stories to test component appearance against different backgrounds.
Implementation
The addon uses a decorator pattern (packages/ondevice-backgrounds/src/index.tsx):
import * as React from 'react' ;
import { makeDecorator } from 'storybook/internal/preview-api' ;
import { addons } from 'storybook/manager-api' ;
import Events from './constants' ;
import Container from './container' ;
export interface Background {
name : string ;
value : string ;
}
export const withBackgrounds = makeDecorator ({
name: 'withBackgrounds' ,
parameterName: 'backgrounds' ,
skipIfNoParametersOrOptions: true ,
wrapper : ( getStory , context , { options , parameters }) => {
const data = ( parameters || options || { values: [] }) as {
default ?: string ;
values : Background [];
};
const backgrounds : Background [] = data . values ;
let background = 'transparent' ;
if ( backgrounds . length !== 0 ) {
addons . getChannel (). emit ( Events . SET , backgrounds );
const defaultValue = data . default
? backgrounds . find (( b ) => b . name === data . default )
: undefined ;
const defaultOrFirst = defaultValue ? defaultValue : backgrounds [ 0 ];
if ( defaultOrFirst ) {
background = defaultOrFirst . value ;
}
}
return (
< Container initialBackground = { background } channel = { addons . getChannel () } >
{ getStory ( context ) as React . ReactNode }
</ Container >
);
},
});
Basic Configuration
import type { StoryObj , Meta } from '@storybook/react-native' ;
import { Text , StyleSheet } from 'react-native' ;
const Background = () => (
< Text style = { styles . text } > Change background via Addons </ Text >
);
const styles = StyleSheet . create ({
text: { color: 'black' },
});
const meta = {
component: Background ,
parameters: {
backgrounds: {
options: {
warm: { name: 'Warm' , value: 'hotpink' },
cool: { name: 'Cool' , value: 'deepskyblue' },
white: { name: 'White' , value: 'white' },
black: { name: 'Black' , value: 'black' },
},
},
},
} satisfies Meta < typeof Background >;
export default meta ;
type Story = StoryObj < typeof meta >;
export const Basic : Story = {
globals: {
backgrounds: { value: 'warm' },
},
};
Feature Flag
Enable backgrounds via feature flag in main.ts:
// .rnstorybook/main.ts
import type { StorybookConfig } from '@storybook/react-native' ;
const main : StorybookConfig = {
stories: [ '../components/**/*.stories.?(ts|tsx|js|jsx)' ],
addons: [
'@storybook/addon-ondevice-controls' ,
'@storybook/addon-ondevice-actions' ,
'@storybook/addon-ondevice-notes' ,
],
features: {
ondeviceBackgrounds: true , // Enable backgrounds addon
},
framework: '@storybook/react-native' ,
};
export default main ;
Notes Addon
The Notes addon renders Markdown documentation alongside your stories, perfect for usage examples and API documentation.
Full Markdown Support
import type { StoryObj , Meta } from '@storybook/react-native' ;
import { View , StyleSheet , Text } from 'react-native' ;
const NotesExampleMeta : Meta < any > = {
parameters: {
notes: `
# H1
## H2
### H3
This is a paragraph that can span multiple lines. It should be line-wrapped
but not contain any paragraph breaks.
Unless a paragraph break is explicitly used.
Inline content can be **strong**, _emphasized_, ~~struck out~~, \` code \` , or a [hyperlink](http://example.com).
---
- Unordered lists are not numbered
- And can be nested
+ As deeply as desired
- And then resume afterwards
---
1. Ordered lists are numbered
2. And can be nested too
1. Also as deeply as desired
3. And then resume afterwards
---
\`\`\` tsx
Code fences are blocks of monospace text
where leading whitespace is preserved,
and **inline** markup is not supported.
\`\`\`
---
> Block quotes are blocks of normal text
> where **inline** markup is possible and
>
>> can be nested too.
>
> - Block content is even possible!
` ,
},
};
export default NotesExampleMeta ;
type NotesExampleStory = StoryObj < any >;
function NotesContent () {
return (
< View style = { styles . container } >
< Text > This story exercises the notes addon, see the addons panel. </ Text >
</ View >
);
}
const styles = StyleSheet . create ({
container: {
... StyleSheet . absoluteFillObject ,
alignItems: 'center' ,
justifyContent: 'center' ,
padding: 16 ,
},
});
export const NotesExample : NotesExampleStory = () => < NotesContent /> ;
Component Documentation
Use notes for component API documentation:
import type { Meta , StoryObj } from '@storybook/react-native' ;
import { ActionButton } from './Actions' ;
import { fn } from 'storybook/test' ;
const meta = {
component: ActionButton ,
parameters: {
notes: `
# Button
This is a button component.
You use it like this:
\`\`\` tsx
<Button
text="Press me!"
onPress={() => console.log('pressed')}
/>
\`\`\`
## Props
- **text**: string - The button label
- **onPress**: () => void - Called when button is pressed
` ,
},
} satisfies Meta < typeof ActionButton >;
export default meta ;
Advanced: Creating Custom Addons
You can create custom on-device addons following the same pattern. Here’s the basic structure:
// Custom addon registration
import { addons , types } from 'storybook/manager-api' ;
export const ADDON_ID = 'MY_ADDON' ;
export const PANEL_ID = ` ${ ADDON_ID } /panel` ;
export const PARAM_KEY = 'myAddon' ;
export function register () {
addons . register ( ADDON_ID , ( api ) => {
addons . add ( PANEL_ID , {
type: types . PANEL ,
title: 'My Addon' ,
render : ({ active }) => (
< AddonPanel active = { active } >
< MyAddonUI api = { api } />
</ AddonPanel >
),
paramKey: PARAM_KEY ,
});
});
}
See the example local addon at examples/expo-example/.rnstorybook/local-addon-example.
Custom addons must use the on-device addon API. Web-based addons won’t work in React Native.
Best Practices
Install only what you need : Each addon adds to bundle size
Use Controls for interactive demos : Let users explore component variants
Document with Notes : Add usage examples and API docs
Test with Backgrounds : Ensure components work on different backgrounds
Combine addons : Use Controls + Actions to test and debug interactivity
Next Steps
Testing with Stories Reuse your stories in unit tests with portable stories
Writing Stories Learn more about Component Story Format