# How to Use the Storybook Actions Addon for Logging User Interactions

> Learn to use the Storybook Actions addon to log user interactions like clicks and changes. Capture events for real-time debugging in a dedicated panel. Enhance your development workflow.

- Repository: [Storybook/storybook](https://github.com/storybookjs/storybook)
- Tags: how-to-guide
- Published: 2026-02-27

---

**The Storybook Actions addon captures user events such as clicks, changes, and submits by creating handler functions that serialize arguments and emit them to the Actions panel for real-time debugging.**

The Actions addon in the storybookjs/storybook repository transforms any user interaction into a structured log entry visible in Storybook's **Actions** panel. By leveraging Storybook's channel system (`storybook/actions/action-event`), the addon bridges the preview iframe and manager UI to display serialized event data. This guide explains the architecture, configuration options, and implementation patterns based on the source code in the `next` branch.

## How the Actions Addon Works

### Core Architecture Components

The addon consists of two runtime contexts: the **preview** side where handlers execute, and the **manager** side where logs render. Key files in `code/core/src/actions/` define this behavior:

- **[`manager.tsx`](https://github.com/storybookjs/storybook/blob/main/manager.tsx)** – Registers the addon panel using `addons.register` under the `storybook/actions` namespace
- **[`constants.ts`](https://github.com/storybookjs/storybook/blob/main/constants.ts)** – Defines `EVENT_ID` (`storybook/actions/action-event`), `PANEL_ID`, and `CLEAR_ID` for channel communication
- **[`runtime/action.ts`](https://github.com/storybookjs/storybook/blob/main/runtime/action.ts)** – Implements the `action()` factory that creates logging handlers
- **[`runtime/actions.ts`](https://github.com/storybookjs/storybook/blob/main/runtime/actions.ts)** – Provides the `actions()` helper for generating multiple named handlers
- **[`runtime/configureActions.ts`](https://github.com/storybookjs/storybook/blob/main/runtime/configureActions.ts)** – Stores mutable default configuration (`depth`, `limit`, `clearOnStoryChange`)
- **[`decorator.ts`](https://github.com/storybookjs/storybook/blob/main/decorator.ts)** – Contains the deprecated `withActions` decorator for delegated event listening
- **[`containers/ActionLogger/index.tsx`](https://github.com/storybookjs/storybook/blob/main/containers/ActionLogger/index.tsx)** – Manager-side container that listens for channel events and manages state

### Event Flow from Handler to Panel

When a user interacts with a component, the data flows through four stages:

1. **Handler Execution** – The `action()` function in [`code/core/src/actions/runtime/action.ts`](https://github.com/storybookjs/storybook/blob/main/code/core/src/actions/runtime/action.ts) returns a callback that serializes arguments using the configured `depth` limit
2. **Channel Emission** – The handler calls `addons.getChannel().emit(EVENT_ID, payload)` with the action name, serialized args, and display options
3. **State Aggregation** – The `ActionLogger` container receives the payload via `api.on(EVENT_ID, ...)` and coalesces identical calls while respecting the `limit` parameter
4. **UI Rendering** – The component in [`code/core/src/actions/components/ActionLogger/index.tsx`](https://github.com/storybookjs/storybook/blob/main/code/core/src/actions/components/ActionLogger/index.tsx) renders the action name, argument tree, and call count in a scrollable table

## Setting Up Action Handlers

### The `action()` Function for Single Events

Import `action` from `@storybook/addon-actions` to create a named handler for individual events. This is the most common pattern for logging specific callbacks:

```tsx
import { action } from '@storybook/addon-actions';

const onSubmit = action('form-submit');

export const Form = () => (
  <form onSubmit={onSubmit}>
    <input name="email" placeholder="Email" />
    <button type="submit">Send</button>
  </form>
);

```

When the form submits, the **form-submit** entry appears in the Actions panel with serialized event data.

### The `actions()` Helper for Multiple Events

For components with multiple callbacks, use the `actions()` utility from [`code/core/src/actions/runtime/actions.ts`](https://github.com/storybookjs/storybook/blob/main/code/core/src/actions/runtime/actions.ts) to generate a map of handlers:

```tsx
import { actions } from '@storybook/addon-actions';

export const Counter = () => {
  const { increment, decrement } = actions('increment', 'decrement');

  return (
    <div>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
};

```

Each string argument creates a distinct handler that logs under that name.

## Configuring the Actions Addon

### Global Configuration with `configureActions()`

Modify serialization behavior globally by calling `configureActions()` in your [`.storybook/preview.ts`](https://github.com/storybookjs/storybook/blob/main/.storybook/preview.ts) file. This mutates the shared config object used by all handlers:

```tsx
import { configureActions } from '@storybook/addon-actions';

configureActions({
  depth: 3,          // Maximum object nesting level displayed
  limit: 20,         // Maximum actions stored in the panel
  clearOnStoryChange: true, // Reset logs when switching stories
});

```

The configuration is defined in [`code/core/src/actions/runtime/configureActions.ts`](https://github.com/storybookjs/storybook/blob/main/code/core/src/actions/runtime/configureActions.ts) and applies to every `action()` call in the project.

### Controlling Serialization Depth

Limit deep object logging to keep the Actions panel readable:

```tsx
import { action, configureActions } from '@storybook/addon-actions';

configureActions({ depth: 2 });

export const ComplexObject = () => {
  const log = action('object-click');

  return (
    <button
      onClick={() =>
        log({ 
          user: { 
            name: 'Alice', 
            profile: { 
              age: 30, 
              address: { city: 'NY' } 
            } 
          } 
        })
      }
    >
      Log Object
    </button>
  );
};

```

Objects nested deeper than level 2 display as `[Object]` to prevent UI clutter.

## Delegated Event Handling

### Using the `withActions` Decorator (Deprecated)

The `withActions` decorator in [`code/core/src/actions/decorator.ts`](https://github.com/storybookjs/storybook/blob/main/code/core/src/actions/decorator.ts) provides delegated event listening without manual handler wiring. Although deprecated and scheduled for removal in Storybook v10, it demonstrates advanced event delegation patterns:

```tsx
import { withActions } from '@storybook/addon-actions';

export default {
  title: 'Button/Delegated',
  decorators: [withActions],
  parameters: {
    actions: { 
      handles: ['click .trackable', 'change input'] 
    },
  },
};

export const Delegated = () => (
  <div>
    <button className="trackable">Tracked Click</button>
    <button>Ignored Click</button>
    <input type="text" placeholder="Tracked Input" />
  </div>
);

```

The decorator attaches a single listener to the Storybook root element and forwards matching events to the action logger based on the CSS selectors in `parameters.handles`.

## Summary

- **The Actions addon** creates handler functions via `action()` and `actions()` that serialize arguments and emit `storybook/actions/action-event` through Storybook's channel system
- **Configuration** is managed through `configureActions()` in [`code/core/src/actions/runtime/configureActions.ts`](https://github.com/storybookjs/storybook/blob/main/code/core/src/actions/runtime/configureActions.ts), controlling `depth`, `limit`, and `clearOnStoryChange` behavior
- **Manager-side rendering** occurs in [`code/core/src/actions/containers/ActionLogger/index.tsx`](https://github.com/storybookjs/storybook/blob/main/code/core/src/actions/containers/ActionLogger/index.tsx), which aggregates identical calls and respects display limits
- **Delegated handling** via `withActions` is deprecated but available for legacy use cases requiring CSS selector-based event capture
- All handlers serialize data according to the global config before transmission to the Actions panel

## Frequently Asked Questions

### How do I prevent the Actions panel from showing duplicate events?

The `ActionLogger` component automatically coalesces identical consecutive calls and displays a call count badge. To clear the log manually, click the **Clear** button in the Actions panel, which emits the `CLEAR_ID` event defined in [`code/core/src/actions/constants.ts`](https://github.com/storybookjs/storybook/blob/main/code/core/src/actions/constants.ts). Setting `clearOnStoryChange: true` in `configureActions()` resets the log automatically when navigating between stories.

### What is the difference between `action()` and `actions()` in Storybook?

`action()` (from [`code/core/src/actions/runtime/action.ts`](https://github.com/storybookjs/storybook/blob/main/code/core/src/actions/runtime/action.ts)) creates a single named handler function, ideal for one-off events like form submissions. `actions()` (from [`code/core/src/actions/runtime/actions.ts`](https://github.com/storybookjs/storybook/blob/main/code/core/src/actions/runtime/actions.ts)) accepts multiple string arguments and returns an object mapping those names to individual handlers, useful for components with several callbacks. Both use the same underlying serialization and channel emission logic.

### Why are my logged objects showing as `[Object]` instead of expanded data?

This occurs when the object depth exceeds the configured `depth` limit. Call `configureActions({ depth: 5 })` or higher in your preview configuration to expand deeper nesting. The default depth is conservative to prevent performance issues when logging large event objects or DOM nodes.

### Is the `withActions` decorator still supported in Storybook?

The `withActions` decorator is deprecated as of recent Storybook versions and will be removed in v10. While it still functions for delegated event handling via `parameters.handles`, the recommended approach is to use explicit `action()` handlers assigned directly to component props for clearer data flow and better TypeScript support.