How to Use the Storybook Controls Addon for Interactive Component Props

The Controls addon automatically generates a UI panel that lets you edit component props in real-time by mapping your story's args and argTypes to interactive form fields.

The Controls addon is a core feature of the Storybook ecosystem that transforms static component documentation into live, editable playgrounds. By declaring argTypes with control configurations in your story files, you enable the controls addon for interactive component props without writing any additional UI code. This article explains the implementation details based on the storybookjs/storybook source code.

What Is the Controls Addon?

The Controls addon bridges the gap between your component's prop interface and Storybook's UI. It reads metadata from your stories and renders form controls—text inputs, toggles, color pickers, and more—based on the control field in your argTypes definition.

According to the source code in code/core/src/controls/README.md, the addon consists of two main parts: the manager side (the UI panel) and the preview side (the component rendering). The manager uses useArgTypes() to collect metadata and renders an ArgsTable, while the preview uses useArgs() to sync state changes back to the component.

Setting Up Interactive Props with argTypes

To enable the controls addon for interactive component props, you define argTypes in your story's default export. Each key in argTypes corresponds to a component prop, and the control property specifies which UI element to render.

Declaring Control Types

The basic structure follows this pattern in your story file:

// Button.stories.ts
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  component: Button,
  argTypes: {
    label: { control: 'text' },
    disabled: { control: 'boolean' },
    variant: {
      control: { type: 'select', options: ['primary', 'secondary', 'tertiary'] },
    },
    backgroundColor: { control: 'color' },
    count: {
      control: { type: 'range', min: 0, max: 100, step: 10 },
    },
  },
};

export default meta;

In code/core/src/controls/manager.tsx, the useArgTypes() hook collects these definitions and passes them to the ArgsTable component. The table renders the appropriate input based on the control.type value.

Supported Control Types

The Controls addon supports a comprehensive set of input types defined in the source. According to code/core/template/stories/controls/basics.stories.ts, the available controls include:

  • boolean – Renders a checkbox toggle
  • text – Standard text input field
  • number – Numeric input with optional min/max constraints
  • range – Slider control for numeric values
  • select – Dropdown menu with predefined options
  • radio – Radio button group for exclusive selection
  • color – Color picker supporting hex, RGB, and HSL values
  • date – Date picker input
  • file – File upload input
  • object – JSON editor for complex data structures
  • array – Specialized input for array values

Each control type accepts additional configuration options. For example, the range control accepts min, max, and step parameters, while select accepts an options array.

How Controls Work Under the Hood

Understanding the architecture helps when debugging or extending the controls addon for interactive component props. The implementation splits responsibilities between the manager (UI) and preview (canvas) environments.

Manager Side (UI)

The manager side code lives in code/core/src/controls/manager.tsx. This file registers the addon panel using the ADDON_ID constant defined in code/core/src/controls/constants.ts ('storybook/controls').

Key implementation details from the source:

  1. The useArgTypes() hook retrieves the argTypes metadata from the current story
  2. The ArgsTable component renders the control inputs based on this metadata
  3. When a user interacts with a control, the manager calls updateArgs() via the useArgs() hook
  4. This triggers a message to the preview iframe to re-render the component with new args

The ControlsPanel component in code/core/src/controls/components/ControlsPanel.tsx handles the actual rendering. It checks if the current story has argTypes with controls, and only displays the panel when relevant controls exist.

Preview Side (Component Rendering)

The preview side is minimal by design. The file code/core/src/controls/preview.ts simply ensures that args are passed correctly to the component. As shown in the source:

// code/core/src/controls/preview.ts (simplified)
export const parameters = {
  controls: {
    expanded: true, // Show full documentation for each arg
  },
};

The preview does not require explicit configuration because the Storybook framework automatically maps the args object to component props. When the manager sends updated args via the channel, the preview re-renders the story with the new values.

Complete Working Example

Here is a complete, runnable example demonstrating the controls addon for interactive component props with a React component.

First, the component definition:

// src/components/Card.tsx
import React from 'react';

interface CardProps {
  title: string;
  description?: string;
  isActive?: boolean;
  variant?: 'elevated' | 'outlined' | 'flat';
  maxWidth?: number;
  backgroundColor?: string;
}

export const Card = ({
  title,
  description = '',
  isActive = false,
  variant = 'elevated',
  maxWidth = 300,
  backgroundColor = '#ffffff',
}: CardProps) => {
  return (
    <div
      style={{
        maxWidth: `${maxWidth}px`,
        padding: '20px',
        borderRadius: variant === 'elevated' ? '8px' : '0',
        border: variant === 'outlined' ? '2px solid #ccc' : 'none',
        boxShadow: variant === 'elevated' ? '0 4px 6px rgba(0,0,0,0.1)' : 'none',
        backgroundColor,
        opacity: isActive ? 1 : 0.6,
      }}
    >
      <h3>{title}</h3>
      {description && <p>{description}</p>}
    </div>
  );
};

Next, the story configuration with controls:

// src/components/Card.stories.ts
import type { Meta, StoryObj } from '@storybook/react';
import { Card } from './Card';

const meta: Meta<typeof Card> = {
  title: 'Components/Card',
  component: Card,
  parameters: {
    controls: {
      expanded: true, // Show full documentation for each control
    },
  },
  argTypes: {
    title: {
      control: 'text',
      description: 'The header text displayed on the card',
    },
    description: {
      control: 'text',
      description: 'Optional body text',
    },
    isActive: {
      control: 'boolean',
      description: 'Whether the card is in an active state',
    },
    variant: {
      control: {
        type: 'select',
        options: ['elevated', 'outlined', 'flat'],
      },
      description: 'Visual style variant',
    },
    maxWidth: {
      control: {
        type: 'range',
        min: 200,
        max: 800,
        step: 50,
      },
      description: 'Maximum width in pixels',
    },
    backgroundColor: {
      control: 'color',
      description: 'Background color of the card',
    },
  },
};

export default meta;

type Story = StoryObj<typeof Card>;

export const Default: Story = {
  args: {
    title: 'Card Title',
    description: 'This is an interactive example of the controls addon.',
    isActive: true,
    variant: 'elevated',
    maxWidth: 400,
    backgroundColor: '#f0f0f0',
  },
};

export const Outlined: Story = {
  args: {
    title: 'Outlined Card',
    variant: 'outlined',
    isActive: false,
    maxWidth: 300,
  },
};

When you run Storybook, the Controls panel appears automatically. Changing any value in the panel instantly updates the component preview without reloading the page.

Advanced Features

Beyond basic prop editing, the controls addon for interactive component props includes several advanced capabilities for power users.

Saving Story Changes

During development, you can persist control changes directly back to your source files. The manager side implements a "Save story" button in code/core/src/controls/components/SaveStory.tsx. When clicked, it emits SAVE_STORY_REQUEST events via the Storybook channel.

As implemented in code/core/src/controls/manager.tsx, this feature is only available in development mode. It allows the addon to write updated args back to the story file, eliminating the need to manually copy values from the UI to your code.

Custom Controls Panels

If you need specialized behavior beyond the default panel, you can build custom controls using the same underlying hooks. The ControlsPanel component in code/core/src/controls/components/ControlsPanel.tsx demonstrates the standard pattern:

  1. Retrieve current story data via api.getCurrentStoryData()
  2. Load argTypes using useArgTypes()
  3. Render ArgsTable with updateArgs callback
  4. Call updateArgs when users interact with controls to trigger preview re-renders

You can import useArgs and useArgTypes from @storybook/manager-api to build alternative interfaces while maintaining the same reactive data flow.

Summary

The Controls addon transforms Storybook from a static documentation tool into an interactive playground. Key takeaways include:

Frequently Asked Questions

How do I disable the Controls panel for a specific story?

You can disable the Controls addon for individual stories or components by setting the controls parameter to disable: true in your story export:

export const NoControls = {
  parameters: {
    controls: { disable: true },
  },
};

This prevents the Controls panel from appearing for that specific story while keeping it available elsewhere in your Storybook.

Why aren't my controls showing up in the panel?

If controls are missing, you likely haven't defined argTypes with a control field, or your component props aren't being automatically inferred. Storybook automatically generates controls for props it can detect, but for complex types or when using JavaScript instead of TypeScript, you must manually specify argTypes in your default export as shown in the code/core/src/controls/components/ControlsPanel.tsx implementation.

Can I use Controls with non-React frameworks?

Yes, the Controls addon works with any framework supported by Storybook, including Vue, Angular, Svelte, and Web Components. The argTypes and args API is framework-agnostic. The preview side (code/core/src/controls/preview.ts) simply passes the args object to your component, regardless of framework, while the manager side renders the same React-based UI for the controls panel.

How do I create custom control types beyond the built-in options?

While Storybook provides built-in controls like text, boolean, and select, you can extend functionality using the control configuration's type property with custom matchers or by creating addon panels that use the useArgs() hook from @storybook/manager-api. For most use cases, combining the object control with JSON editing or using the file control for asset selection provides sufficient flexibility without requiring custom implementations.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →