Skip to main content
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:
argTypes: {
  age: {
    step: 5,
    min: 0,
    max: 90,
    range: true,
  },
}

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

  1. Install only what you need: Each addon adds to bundle size
  2. Use Controls for interactive demos: Let users explore component variants
  3. Document with Notes: Add usage examples and API docs
  4. Test with Backgrounds: Ensure components work on different backgrounds
  5. 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

Build docs developers (and LLMs) love