Using Storybook Controls to Edit Component Props Live
Learn how to add Storybook Controls, configure args, and adjust component props in real time while avoiding common pitfalls.
03 Mar 2026, 11:02 UTC

Quick answer
Storybook Controls adds a panel to the Storybook UI that lets you change a component’s props in real time. You install the addon, declare it in .storybook/main.js, and write a story that spreads args onto the component. Controls then generate UI controls (text fields, color pickers, toggles, selects, etc.) based on the type of each arg.
How to set it up
1. Install the addon
Run the command in your project’s root directory. No special permissions are required; a typical developer account with npm/yarn access is sufficient.
# npm
npm install --save-dev @storybook/addon-controls
# or yarn
yarn add --dev @storybook/addon-controls
Verify that @storybook/addon-controls appears in the devDependencies section of package.json.
2. Register the addon
Edit .storybook/main.js (create the file if you are starting from npx sb init) and add the addon to the addons array.
// .storybook/main.js
module.exports = {
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-controls'],
};
After saving, restart Storybook if it is already running.
3. Write a story that uses args
Assume a simple Button component that accepts label, primary (boolean), and bgColor (string).
// src/Button.js
export const Button = ({ label, primary, bgColor }) => {
const style = {
background: bgColor || (primary ? '#0070f3' : '#fff'),
color: primary ? '#fff' : '#000',
padding: '0.5rem 1rem',
border: 'none',
cursor: 'pointer',
};
return {label};
};
Now create a story file.
// src/Button.stories.js
import { Button } from './Button';
export default {
title: 'Example/Button',
component: Button,
};
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = {
label: 'Click me',
primary: true,
bgColor: '#0070f3',
};
// Optional: fine‑tune how Controls renders a specific arg
export const PrimaryWithCustom = Template.bind({});
PrimaryWithCustom.args = Primary.args;
PrimaryWithCustom.argTypes = {
bgColor: { control: { type: 'color' } },
};
When Storybook starts, a “Controls” tab appears below the canvas. Each arg gets a matching UI control: label → text input, primary → toggle, bgColor → color picker (or the custom color picker we defined). Changing any control updates the component instantly.
Limits of Controls
- Plain objects only: Controls works with serializable values passed via
args. If a prop is derived from React context, a hook, or a computed value that isn’t inargs, the UI cannot affect it. - Complex values: Objects, arrays, functions,
Dateinstances, or class instances are shown as a raw JSON editor unless you provide a customargTypewith a serializer. Deep changes to nested objects may not trigger a re‑render in some frameworks. - Performance overhead: Each control adds a small amount of runtime work. In very large storybooks with dozens of controls per story, startup time can noticeably increase.
- No production validation: Controls are a development aid; they do not replace
PropTypes, TypeScript checks, or runtime validation.
Common mistakes and how to avoid them
- Forgotten addon registration: If you omit
@storybook/addon-controlsfrom.storybook/main.js, the Controls tab will not appear. Double‑check theaddonsarray after installation. - Mutating
argsinside the story function: Writingargs.label = 'new'mutates the shared args object and breaks arg isolation across stories. Always treatargsas read‑only and spread them into the component. - Using non‑serializable values as args: Passing a
Dateobject or a class instance causes Controls to fall back to a JSON editor, which is cumbersome and may not update the component correctly. Convert such values to primitives (e.g., timestamp strings) or provide a custom serializer. - Over‑reliance on Controls for edge cases: Controls cannot simulate prop changes that happen asynchronously (e.g., data fetched in a useEffect). For those scenarios, write separate stories that mock the async behavior.
Verification steps
- Clone a fresh Storybook project:
npx sb init my-test-appandcd my-test-app. - Install the addon:
npm install --save-dev @storybook/addon-controls. - Add the addon to
.storybook/main.jsas shown above. - Create a simple component (e.g.,
src/Button.js) and its story (src/Button.stories.js) using theargspattern. - Start Storybook:
npm run storybook(oryarn storybook). - Open the story in the browser; locate the “Controls” panel below the canvas. Verify that each
arghas an appropriate control and that changing a control updates the rendered component instantly.
If the Controls tab is missing, check the browser console for errors about missing addons and confirm that @storybook/addon-controls is listed in package.json devDependencies.
Practical way to check the result
After adjusting a control, observe the component directly in the Storybook canvas. For a boolean toggle like primary, the button’s background color should switch between the primary and default shades. For a text field like label, the button’s label should change instantly. If the UI does not update, open the browser’s developer tools and verify that the component is receiving the new props (you can add a temporary console.log in the component to confirm).
Mitigating complex‑object limitations
When you need to pass an object or array as a prop, define an explicit argType with a serializer:
export const ObjectStory = Template.bind({});
ObjectStory.args = {
options: { theme: 'dark', size: 'large' },
};
ObjectStory.argTypes = {
options: {
control: { type: 'object' }, // shows a JSON editor
// optional: provide a custom serializer
// serializer: (value) => JSON.stringify(value, null, 2),
},
};
Keep the object shallow if possible; deep mutations may not trigger a re‑render depending on the framework’s change detection. For deep updates, consider splitting the object into multiple primitive args or using a custom wrapper component that spreads the object’s keys.
Summary
Storybook Controls gives you a live UI for tweaking component props, speeding up visual testing and design collaboration. Install the addon, register it, write stories that spread args, and use the generated controls to experiment. Remember the limits—plain serializable values only, avoid mutating args, and watch for performance impact in large storybooks. With these practices, Controls becomes a reliable part of your component development workflow.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.