Join live session: Top 8 Storybook myths holding your team back
Docs
Storybook Docs

Controls

Watch a video tutorial

Storybook Controls gives you a graphical UI to interact with a component's arguments dynamically without needing to code. It creates an addon panel next to your component examples ("stories"), so you can edit them live.

Controls do not require any modification to your components. Stories for controls are:

  • Convenient. Auto-generate controls based on React/Vue/Angular/etc. components.
  • Portable. Reuse your interactive stories in documentation, tests, and even in designs.
  • Rich. Customize the controls and interactive data to suit your exact needs.

To use the Controls addon, you need to write your stories using args. Storybook will automatically generate UI controls based on your args and what it can infer about your component. Still, you can configure the controls further using argTypes, see below.

If you have stories in the older pre-Storybook 6 style, check the args & controls migration guide to learn how to convert your existing stories for args.

Choosing the control type

By default, Storybook will choose a control for each arg based on the initial value of the arg. It works well with certain types of args, such as boolean values or free-text strings, but in other cases, you want a more restricted control.

To use auto-detected controls with React, you must fill in the component field in your story metadata:

// Button.stories.js|jsx|ts|tsx
 
import { Button } from './Button';
 
export default {
  title: 'Button',
  component: Button,
};

Storybook uses this to auto-generate the ArgTypes for your component based on either PropTypes (using react-docgen) or TypeScript types (using react-docgen-typescript).

For instance, suppose you have a variant arg on your story that should be primary or secondary:

Button.stories.js|jsx|ts|tsx
import { Button } from './Button';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'Button',
  component: Button,
};
 
const Template = (args) => ({
  //๐Ÿ‘‡ Your template goes here
});
 
export const Primary = Template.bind({});
Primary.args = {
  variant: 'primary',
};
 
export const Primary = {
  args: {
    variant: 'primary',
  },
};

By default, Storybook will render a free text input for the variant arg:

Essential addon Controls using a string

It works as long as you type a valid string into the auto-generated text control. Still, it's not the best UI for our scenario, given that the component only accepts primary or secondary as variants. Letโ€™s replace it with Storybookโ€™s radio component.

We can specify which controls get used by declaring a custom argType for the variant property. ArgTypes encode basic metadata for args, such as name, description, defaultValue for an arg. These get automatically filled in by Storybook Docs.

ArgTypes can also contain arbitrary annotations, which the user can override. Since variant is a property of the component, let's put that annotation on the default export.

Button.stories.js|jsx|ts|tsx
import { Button } from './Button';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'Button',
  component: Button,
  argTypes: {
    variant: {
      options: ['primary', 'secondary'],
      control: { type: 'radio' },
    },
  },
};

ArgTypes are a powerful feature that can be used to customize the controls for your stories. See the documentation about customizing controls with argTypes annotation for more information.

This replaces the input with a radio group for a more intuitive experience.

Essential Control addon with a radio group

Custom control type matchers

For a few types, Controls can automatically be inferred with regex. If you've used the Storybook CLI to setup your project it should have automatically created the following defaults in .storybook/preview.js:

Data typeDefault regexDescription
color/(background|color)$/iWill display a color picker UI for the args that match it
date/Date$/Will display a date picker UI for the args that match it

If you haven't used the CLI to setup the configuration, or if you want to define your own patterns, use the matchers property in the controls parameter:

.storybook/preview.js
export const parameters = {
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
};

Fully custom args

Until now, we only used auto-generated controls based on the component we're writing stories for. If we are writing complex stories, we may want to add controls for args that arenโ€™t part of the component.

Table.stories.js|jsx!ts!tsx
import React from 'react';
 
import { Table } from './Table';
import { TD } from './TableDataCell';
import { TR } from './TableRow';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'Custom Table',
  component: Table,
};
 
const TableStory = ({ data, ...args}) => (
  <Table {...args}>
    {data.map((row) => (
      <TR>
        {row.map((item) => (
          <TD>{item}</TD>
        ))}
      </TR>
    ))}
  </Table>
);
 
export const Numeric = TableStory.bind({});
Numeric.args = {
  //๐Ÿ‘‡ This arg is for the story component
  data: [[1, 2, 3], [4, 5, 6]],
  //๐Ÿ‘‡ The remaining args get passed to the `Table` component
  size: 'large',
};

By default, Storybook will add controls for all args that:

Using argTypes, you can change the display and behavior of each control.

Dealing with complex values

When dealing with non-primitive values, you'll notice that you'll run into some limitations. The most obvious issue is that not every value can be represented as part of the args param in the URL, losing the ability to share and deeplink to such a state. Beyond that, complex values such as JSX cannot be synchronized between the manager (e.g., Controls addon) and the preview (your story).

One way to deal with this is to use primitive values (e.g., strings) as arg values and add a custom render function to convert these values to their complex counterpart before rendering. It isn't the nicest way to do it (see below), but certainly the most flexible.

YourComponent.stories.ts|tsx
import React from 'react';
 
import { ComponentStory, ComponentMeta } from '@storybook/react';
 
import { YourComponent } from './your-component';
 
//๐Ÿ‘‡ Some function to demonstrate the behavior
const someFunction = (valuePropertyA, valuePropertyB) => {
  // Makes some computations and returns something
};
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'YourComponent',
  //๐Ÿ‘‡ Creates specific argTypes with options
  argTypes: {
    propertyA: {
      options: ['Item One', 'Item Two', 'Item Three'],
      control: { type: 'select' } // Automatically inferred when 'options' is defined
    },
    propertyB: {
      options: ['Another Item One', 'Another Item Two', 'Another Item Three'],
    },
  },
} as ComponentMeta<typeof YourComponent>;
 
const Template: ComponentStory<typeof YourComponent> = ({ propertyA, propertyB, ...rest }) => {
  //๐Ÿ‘‡ Assigns the result from the function to a variable
  const someFunctionResult = someFunction(propertyA, propertyB);
 
  return <YourComponent someProperty={someFunctionResult} {...rest} />;
};
 
export const ExampleStory = Template.bind({});
ExampleStory.args= {
  propertyA: 'Item One',
  propertyB: 'Another Item One',
};

Unless you need the flexibility of a function, an easier way to map primitives to complex values before rendering is to define a mapping, additionally, you can specify control.labels to configure custom labels for your checkbox, radio, or select input.

Button.stories.js|jsx|ts|tsx
import { Button } from './Button';
import { ArrowUp, ArrowDown, ArrowLeft, ArrowRight } from './icons';
 
const arrows = { ArrowUp, ArrowDown, ArrowLeft, ArrowRight };
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'Button',
  component: Button,
  argTypes: {
    arrow: {
      options: Object.keys(arrows), // An array of serializable values
      mapping: arrows, // Maps serializable option values to complex arg values
      control: {
        type: 'select', // Type 'select' is automatically inferred when 'options' is defined
        labels: {
          // 'labels' maps option values to string labels
          ArrowUp: 'Up',
          ArrowDown: 'Down',
          ArrowLeft: 'Left',
          ArrowRight: 'Right',
        },
      },
    },
  },
};

Note that both mapping and control.labels don't have to be exhaustive. If the currently selected option is not listed, it's used verbatim.

Configuration

The Controls addon can be configured in two ways:

  • Individual controls can be configured via control annotations.
  • The addon's appearance can be configured via parameters.

Annotation

As shown above, you can configure individual controls with the โ€œcontrol" annotation in the argTypes field of either a component or story. Below is a condensed example and table featuring all available controls.

Data TypeControlDescription
booleanbooleanProvides a toggle for switching between possible states.
argTypes: { active: { control: 'boolean' }}
numbernumberProvides a numeric input to include the range of all possible values.
argTypes: { even: { control: { type: 'number', min:1, max:30, step: 2 } }}
rangeProvides a range slider component to include all possible values.
argTypes: { odd: { control: { type: 'range', min: 1, max: 30, step: 3 } }}
objectobjectProvides a JSON-based editor component to handle the object's values.
Also allows edition in raw mode.
argTypes: { user: { control: 'object' }}
arrayobjectProvides a JSON-based editor component to handle the values of the array.
Also allows edition in raw mode.
argTypes: { odd: { control: 'object' }}
fileProvides a file input component that returns an array of URLs.
Can be further customized to accept specific file types.
argTypes: { avatar: { control: { type: 'file', accept: '.png' } }}
enumradioProvides a set of radio buttons based on the available options.
argTypes: { contact: { control: 'radio', options: ['email', 'phone', 'mail'] }}
inline-radioProvides a set of inlined radio buttons based on the available options.
argTypes: { contact: { control: 'inline-radio', options: ['email', 'phone', 'mail'] }}
checkProvides a set of checkbox components for selecting multiple options.
argTypes: { contact: { control: 'check', options: ['email', 'phone', 'mail'] }}
inline-checkProvides a set of inlined checkbox components for selecting multiple options.
argTypes: { contact: { control: 'inline-check', options: ['email', 'phone', 'mail'] }}
selectProvides a drop-down list component to handle single value selection. argTypes: { age: { control: 'select', options: [20, 30, 40, 50] }}
multi-selectProvides a drop-down list that allows multiple selected values. argTypes: { countries: { control: 'multi-select', options: ['USA', 'Canada', 'Mexico'] }}
stringtextProvides a freeform text input.
argTypes: { label: { control: 'text' }}
colorProvides a color picker component to handle color values.
Can be additionally configured to include a set of color presets.
argTypes: { color: { control: { type: 'color', presetColors: ['red', 'green']} }}
dateProvides a datepicker component to handle date selection. argTypes: { startDate: { control: 'date' }}

The date control will convert the date into a UNIX timestamp when the value changes. It's a known limitation that will be fixed in a future release. If you need to represent the actual date, you'll need to update the story's implementation and convert the value into a date object.

Gizmo.stories.js|jsx|ts|tsx
import { Gizmo } from './Gizmo';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
   * See https://storybook.js.org/docs/6/configure#configure-story-loading
   * to learn how to generate automatic titles
   */
  title: 'Gizmo',
  component: Gizmo,
  argTypes: {
    canRotate: {
      control: 'boolean',
    },
    width: {
      control: { type: 'number', min: 400, max: 1200, step: 50 },
    },
    height: {
      control: { type: 'range', min: 200, max: 1500, step: 50 },
    },
    rawData: {
      control: 'object',
    },
    coordinates: {
      control: 'object',
    },
    texture: {
      control: {
        type: 'file',
        accept: '.png',
      },
    },
    position: {
      control: 'radio',
      options: ['left', 'right', 'center'],
    },
    rotationAxis: {
      control: {
        type: 'check',
        options: ['x', 'y', 'z'],
      },
    },
    scaling: {
      control: 'select',
      options: [10, 50, 75, 100, 200],
    },
    label: {
      control: 'text',
    },
    meshColors: {
      control: {
        type: 'color',
        presetColors: ['#ff0000', '#00ff00', '#0000ff'],
      },
    },
    revisionDate: {
      control: 'date',
    },
  },
};

Numeric data types will default to a number control unless additional configuration is provided.

Parameters

Controls supports the following configuration parameters, either globally or on a per-story basis:

Show full documentation for each property

Since Controls is built on the same engine as Storybook Docs, it can also show property documentation alongside your controls using the expanded parameter (defaults to false). This means you embed a complete ArgsTable doc block in the controls panel. The description and default value rendering can be customized in the same way as the doc block.

To enable expanded mode globally, add the following to .storybook/preview.js:

.storybook/preview.js
export const parameters = {
  controls: { expanded: true },
};

And here's what the resulting UI looks like:

Controls addon expanded

Specify initial preset color swatches

For color controls, you can specify an array of presetColors, either on the control in argTypes, or as a parameter under the controls namespace:

.storybook/preview.js
export const parameters = {
  controls: {
    presetColors: [{ color: '#ff4785', title: 'Coral' }, 'rgba(0, 159, 183, 1)', '#fe4a49'],
  },
};

Color presets can be defined as an object with color and title or a simple CSS color string. These will then be available as swatches in the color picker. When you hover over the color swatch, you'll be able to see its title. It will default to the nearest CSS color name if none is specified.

Disable controls for specific properties

Aside from the features already documented here, Controls can also be disabled for individual properties.

Suppose you want to disable Controls for a property called foo in a component's story. The following example illustrates how:

YourComponent.stories.js|jsx|ts|tsx
import { YourComponent } from './YourComponent';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
   * See https://storybook.js.org/docs/6/configure#configure-story-loading
   * to learn how to generate automatic titles
   */
  title: 'YourComponent',
  component: YourComponent,
  argTypes: {
    // foo is the property we want to remove from the UI
    foo: {
      table: {
        disable: true,
      },
    },
  },
};

Resulting in the following change in Storybook UI:

The previous example also removed the prop documentation from the table. In some cases, this is fine. However, sometimes you might want to render the prop documentation, but without a control. The following example illustrates how:

YourComponent.stories.js|jsx|ts|tsx
import { YourComponent } from './YourComponent';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'YourComponent',
  component: YourComponent,
  argTypes: {
    // foo is the property we want to remove from the UI
    foo: {
      control: false,
    },
  },
};

As with other Storybook properties, such as decorators, you can apply the same pattern at a story level for more granular cases.

Conditional controls

In some cases, it's useful to be able to conditionally exclude a control based on the value of another control. Controls supports basic versions of these use cases with the if, which can takes a simple query object to determine whether to include the control.

Consider a collection of "advanced" settings that are only visible when the user toggles an "advanced" toggle.

Button.stories.js
import { Button } from './Button';
export default {
  component: Button,
  title: 'Button',
  argTypes: {
    label: { control: 'text' }, // always shows
    advanced: { control: 'boolean' },
    // below are only included when advanced is true
    margin: { control: 'number', if: { arg: 'advanced' } },
    padding: { control: 'number', if: { arg: 'advanced' } },
    cornerRadius: { control: 'number', if: { arg: 'advanced' } },
  },
};

Or consider a constraint where if the user sets one control value, it doesn't make sense for the user to be able to set another value.

Button.stories.js
import { Button } from './Button';
export default {
  component: Button,
  title: 'Button',
  argTypes: {
    // button can be passed a label or an image, not both
    label: {
      control: 'text',
      if: { arg: 'image', truthy: false },
    },
    image: {
      control: { type: 'select', options: ['foo.jpg', 'bar.jpg'] },
      if: { arg: 'label', truthy: false },
    },
  },
};

The query object must contain either an arg or global target:

fieldtypemeaning
argstringThe ID of the arg to test.
globalstringThe ID of the global to test.

It may also contain at most one of the following operators:

operatortypemeaning
truthybooleanIs the target value truthy?
existsbooleanIs the target value defined?
eqanyIs the target value equal to the provided value?
neqanyIs the target value NOT equal to the provided value?

If no operator is provided, that is equivalent to { truthy: true }.

Hide NoControls warning

If you don't plan to handle the control args inside your Story, you can remove the warning with:

Button.stories.js|jsx|ts|tsx
import { Button } from './Button';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'Button',
  component: Button,
};
 
const Template = (args) => ({
  //๐Ÿ‘‡ Your template goes here
});
 
export const Large = Template.bind({});
Large.parameters = {
  controls: { hideNoControlsWarning: true },
};

Filtering controls

In specific cases, you may require to display only a limited number of controls in the controls panel, or all of them except a particular set.

To make this possible, you can use optional include and exclude configuration fields in the controls parameter, which you can define as an array of strings, or as a regular expression.

Consider the following story snippets:

YourComponent.stories.js|jsx|ts|tsx
import { YourComponent } from './YourComponent';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'YourComponent',
  component: YourComponent,
};
 
const Template = (args) => ({
  //๐Ÿ‘‡ Your template goes here
});
 
 
ArrayInclude = Template.bind({})
ArrayInclude.parameters = { controls: { include: ['foo', 'bar'] } };
 
RegexInclude = Template.bind({})
RegexInclude.parameters = { controls: { include: /^hello*/ } };
 
ArrayExclude = Template.bind({})
ArrayExclude.parameters = { controls: { exclude: ['foo', 'bar'] } };
 
RegexExclude = Template.bind({})
RegexExclude.parameters = { controls: { exclude: /^hello*/ } };

Sorting controls

By default, controls are unsorted and use whatever order the args data is processed in (none). Additionally, you can sort them alphabetically by the arg's name (alpha) or with the required args first (requiredFirst).

Consider the following snippet to force required args first:

YourComponent.stories.js|jsx|ts|tsx
import { YourComponent } from './your-component';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'YourComponent',
  component: YourComponent,
  parameters: { controls: { sort: 'requiredFirst' } },
};