Skip to main content
The Actions addon allows you to log and inspect callbacks and event handlers in your React Native components. It displays the arguments passed to action handlers in an interactive panel.

Installation

npm install @storybook/addon-ondevice-actions

Setup

Register the addon in your .storybook/main.ts:
import type { StorybookConfig } from '@storybook/react-native';

const main: StorybookConfig = {
  addons: [
    '@storybook/addon-ondevice-actions',
  ],
};

export default main;

Usage

Use the fn() function from storybook/test to create action handlers:
import type { Meta, StoryObj } from '@storybook/react-native';
import { fn } from 'storybook/test';
import { Button } from './Button';

const meta = {
  component: Button,
} satisfies Meta<typeof Button>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Basic: Story = {
  args: {
    text: 'Press me!',
    onPress: fn(),
  },
};
When you press the button, the action will be logged in the Actions panel with details about the event.

Action Arguments

Actions automatically capture and display all arguments passed to the handler:
export const WithArguments: Story = {
  args: {
    onPress: fn(),
    onLongPress: fn(),
    onPressIn: fn(),
    onPressOut: fn(),
  },
};
Each action will show:
  • The handler name (e.g., onPress)
  • Arguments passed to the handler
  • Timestamp
  • Call count (if called multiple times with same arguments)

Complex Data Types

Actions handle complex data types gracefully:
import { fn } from 'storybook/test';

export const ComplexActions: Story = {
  args: {
    onSelect: fn(),
    onDataChange: fn(),
  },
  render: (args) => (
    <CustomPicker
      {...args}
      onSelect={(item) => {
        args.onSelect(item);
      }}
      onDataChange={(data) => {
        args.onDataChange({
          values: data.values,
          timestamp: Date.now(),
          user: { id: 123, name: 'John' },
        });
      }}
    />
  ),
};
The Actions panel will display:
  • Objects in an expandable tree view
  • Arrays with their contents
  • Nested data structures
  • Function references

Action Options

Configure action behavior with options:
import { action } from '@storybook/addon-actions';

const meta = {
  component: Button,
  parameters: {
    actions: {
      // Clear actions when navigating to a different story
      clearOnStoryChange: true,
      // Limit number of logged actions
      limit: 10,
    },
  },
} satisfies Meta<typeof Button>;

Parameters

actions
object
Actions addon configuration
clearOnStoryChange
boolean
default:"true"
Clear the actions panel when navigating to a different story.
limit
number
default:"50"
Maximum number of actions to keep in the panel. Older actions are removed when the limit is reached.

Automatic Action Args

Storybook automatically creates actions for props that match common event handler patterns:
const meta = {
  component: Button,
  parameters: {
    actions: { argTypesRegex: '^on[A-Z].*' },
  },
} satisfies Meta<typeof Button>;

// Automatically creates actions for:
// - onPress
// - onLongPress
// - onChange
// - etc.

Examples

Basic Button

import type { Meta, StoryObj } from '@storybook/react-native';
import { fn } from 'storybook/test';
import { Button, Text, TouchableOpacity, StyleSheet } from 'react-native';

interface ActionButtonProps {
  text: string;
  onPress: () => void;
}

const ActionButton = ({ text, onPress }: ActionButtonProps) => (
  <TouchableOpacity style={styles.button} onPress={onPress}>
    <Text style={styles.text}>{text}</Text>
  </TouchableOpacity>
);

const meta = {
  component: ActionButton,
  parameters: {
    notes: `
# Button

This is a button component that logs press events.
Press the button to see actions logged in the Actions panel.
    `,
  },
} satisfies Meta<typeof ActionButton>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Basic: Story = {
  args: {
    text: 'Press me!',
    onPress: fn(),
  },
};

const styles = StyleSheet.create({
  button: {
    backgroundColor: '#3B82F6',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
  },
  text: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
});

Form with Multiple Actions

import { fn } from 'storybook/test';

interface FormProps {
  onSubmit: (data: any) => void;
  onCancel: () => void;
  onChange: (field: string, value: any) => void;
  onValidate: (isValid: boolean) => void;
}

const meta = {
  component: Form,
} satisfies Meta<typeof Form>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Interactive: Story = {
  args: {
    onSubmit: fn(),
    onCancel: fn(),
    onChange: fn(),
    onValidate: fn(),
  },
};

Native Events

import { fn } from 'storybook/test';

interface TextInputProps {
  onChangeText: (text: string) => void;
  onFocus: () => void;
  onBlur: () => void;
  onSubmitEditing: () => void;
}

const meta = {
  component: CustomTextInput,
} satisfies Meta<typeof CustomTextInput>;

export default meta;

type Story = StoryObj<typeof meta>;

export const TextInputExample: Story = {
  args: {
    placeholder: 'Type something...',
    onChangeText: fn(),
    onFocus: fn(),
    onBlur: fn(),
    onSubmitEditing: fn(),
  },
};

Gesture Events

import { fn } from 'storybook/test';
import { GestureResponderEvent } from 'react-native';

interface GestureBoxProps {
  onPress: (event: GestureResponderEvent) => void;
  onLongPress: (event: GestureResponderEvent) => void;
  onPressIn: (event: GestureResponderEvent) => void;
  onPressOut: (event: GestureResponderEvent) => void;
}

const meta = {
  component: GestureBox,
} satisfies Meta<typeof GestureBox>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Gestures: Story = {
  args: {
    onPress: fn(),
    onLongPress: fn(),
    onPressIn: fn(),
    onPressOut: fn(),
  },
};

Actions Panel Features

Expandable Objects

Complex objects are displayed in an expandable tree:
// When this action fires:
onDataChange({
  user: {
    id: 123,
    name: 'John Doe',
    profile: {
      avatar: 'https://...',
      bio: 'Developer',
    },
  },
  timestamp: 1234567890,
});

// The Actions panel shows:
// ▶ onDataChange
//   ▶ user
//     id: 123
//     name: "John Doe"
//     ▶ profile
//       avatar: "https://..."
//       bio: "Developer"
//   timestamp: 1234567890

Call Counting

When the same action is called multiple times with identical arguments, the panel displays a count:
// After clicking 3 times:
// onPress (3x)
//   timestamp: 1234567890

Clear Actions

Use the clear button in the Actions panel to remove all logged actions.

Combining with Controls

Actions work seamlessly with the Controls addon:
import { fn } from 'storybook/test';

const meta = {
  component: Button,
  argTypes: {
    variant: {
      control: { type: 'select' },
      options: ['primary', 'secondary', 'outline'],
    },
    disabled: {
      control: 'boolean',
    },
  },
} satisfies Meta<typeof Button>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Interactive: Story = {
  args: {
    text: 'Press me',
    variant: 'primary',
    disabled: false,
    onPress: fn(), // Action
  },
};
You can change the variant and disabled state with Controls, then press the button to see actions logged.

API Reference

fn()

import { fn } from 'storybook/test';

const handler = fn();
Creates an action handler that logs calls to the Actions panel.

action()

Alternative API for creating actions with options:
import { action } from '@storybook/addon-actions';

const handler = action('onPress', {
  clearOnStoryChange: false,
  limit: 20,
});

ActionDisplay

interface ActionDisplay {
  id: string;
  count: number;
  data: any[];
  options: {
    clearOnStoryChange: boolean;
    limit: number;
  };
}

Build docs developers (and LLMs) love