How to Use the Storybook Actions Addon for Logging User Interactions
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– Registers the addon panel usingaddons.registerunder thestorybook/actionsnamespaceconstants.ts– DefinesEVENT_ID(storybook/actions/action-event),PANEL_ID, andCLEAR_IDfor channel communicationruntime/action.ts– Implements theaction()factory that creates logging handlersruntime/actions.ts– Provides theactions()helper for generating multiple named handlersruntime/configureActions.ts– Stores mutable default configuration (depth,limit,clearOnStoryChange)decorator.ts– Contains the deprecatedwithActionsdecorator for delegated event listeningcontainers/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:
- Handler Execution – The
action()function incode/core/src/actions/runtime/action.tsreturns a callback that serializes arguments using the configureddepthlimit - Channel Emission – The handler calls
addons.getChannel().emit(EVENT_ID, payload)with the action name, serialized args, and display options - State Aggregation – The
ActionLoggercontainer receives the payload viaapi.on(EVENT_ID, ...)and coalesces identical calls while respecting thelimitparameter - UI Rendering – The component in
code/core/src/actions/components/ActionLogger/index.tsxrenders 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:
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 to generate a map of handlers:
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 file. This mutates the shared config object used by all handlers:
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 and applies to every action() call in the project.
Controlling Serialization Depth
Limit deep object logging to keep the Actions panel readable:
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 provides delegated event listening without manual handler wiring. Although deprecated and scheduled for removal in Storybook v10, it demonstrates advanced event delegation patterns:
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()andactions()that serialize arguments and emitstorybook/actions/action-eventthrough Storybook's channel system - Configuration is managed through
configureActions()incode/core/src/actions/runtime/configureActions.ts, controllingdepth,limit, andclearOnStoryChangebehavior - Manager-side rendering occurs in
code/core/src/actions/containers/ActionLogger/index.tsx, which aggregates identical calls and respects display limits - Delegated handling via
withActionsis 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. 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) creates a single named handler function, ideal for one-off events like form submissions. actions() (from 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →