Security News
Introducing the Socket Python SDK
The initial version of the Socket Python SDK is now on PyPI, enabling developers to more easily interact with the Socket REST API in Python projects.
@storybook/addon-controls
Advanced tools
The @storybook/addon-controls package allows developers to interact with component inputs dynamically within the Storybook UI. It provides a way to edit props, slots, styles, and other arguments of the components being tested in real-time. This addon generates a user interface for tweaking these inputs without needing to write any additional code.
Dynamic Props Editing
Allows users to dynamically edit the props of a component from within the Storybook UI. The code sample shows how to set up controls for a Button component, including a color picker for the backgroundColor prop and an action logger for the onClick event.
export default {
title: 'Button',
component: Button,
argTypes: {
backgroundColor: { control: 'color' },
onClick: { action: 'clicked' }
},
};
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = {
primary: true,
label: 'Button',
};
Custom Controls
Enables the creation of custom controls for component arguments, such as dropdowns, checkboxes, and more. The code sample demonstrates how to create a select control for the 'size' prop of an Input component.
export default {
title: 'Form/Input',
component: Input,
argTypes: {
size: {
control: {
type: 'select',
options: ['small', 'medium', 'large']
}
}
}
};
const Template = (args) => <Input {...args} />;
export const Medium = Template.bind({});
Medium.args = {
size: 'medium',
placeholder: 'Type here...'
};
Live Editing of Args
Provides a way to live edit the 'args' of a story, which are the arguments that get passed to the component being rendered. The code sample shows a text control for the 'content' prop of a Panel component.
export default {
title: 'Dashboard/Panel',
component: Panel,
argTypes: {
content: { control: 'text' }
}
};
const Template = (args) => <Panel {...args} />;
export const DefaultPanel = Template.bind({});
DefaultPanel.args = {
content: 'Default content',
};
This package is a predecessor to @storybook/addon-controls and offers similar functionality for changing props and other inputs of components within Storybook. However, addon-controls is more integrated with Storybook's Component Story Format (CSF) and offers a better developer experience with less boilerplate code.
While not providing the same UI for editing component inputs, @storybook/addon-actions allows developers to log and monitor events that occur on interactive components within Storybook. It complements the functionality of addon-controls by providing insight into how components respond to user actions.
This addon allows users to change the background colors behind their components within the Storybook preview. It is different from addon-controls but offers a complementary feature that enhances the visual testing capabilities of Storybook.
Storybook Controls gives you UI to interact with a component's inputs dynamically, without needing to code. It creates an addon panel next to your component examples ("stories"), so you can edit them live.
It does not require any modification to your components, and stories for controls are:
Controls are built on top of Storybook Args, which is an open, standards-based format that enable stories to be reused in a variety of contexts.
Controls replaces Storybook Knobs. It incorporates lessons from years of supporting Knobs on tens of thousands of projects and dozens of different frameworks. We couldn't incrementally fix knobs, so we built a better version.
Controls requires Storybook Docs. If you're not using it already, please install that first.
Next, install the package:
npm install @storybook/addon-controls -D # or yarn
And add it to your .storybook/main.js
config:
module.exports = {
addons: [
'@storybook/addon-docs'
'@storybook/addon-controls'
],
};
Let's see how to write stories that automatically generate controls based on your component properties.
Controls is built on Storybook Args, which is a small, backwards-compatible change to Storybook's Component Story Format.
This section is a step-by-step walkthrough for how to upgrade your stories. It takes you from a starting point of the traditional "no args" stories, to auto-generated args, to auto-generated args with custom controls, to fully custom args if you need them.
Let's start with the following component/story combination, which should look familiar if you're coming from an older version of Storybook.
import React from 'react';
interface ButtonProps {
/** The main label of the button */
label?: string;
}
export const Button = ({ label = 'FIXME' }: ButtonProps) => <button>{label}</button>;
And here's a story that shows that Button component:
import React from 'react';
import { Button } from './Button';
export default { title: 'Button', component: Button };
export const Basic = () => <Button label="hello" />;
After installing the controls addon, you'll see a new tab that shows the component's props, but it doesn't show controls because the story doesn't use args. That's not very useful, but we'll fix that momentarily.
To upgrade your story to an Args story, modify it to accept an args object. NOTE: you may need to refresh the browser at this point.
export const Basic = (args) => {
console.log({ args });
return <Button label="hello" />;
};
Now you'll see auto-generated controls in the Controls
tab, and you can see the args
data updating as you edit the values in the UI:
Since the args directly matches the Button
's props, we can pass it into the args directly:
export const Basic = (args) => <Button {...args} />;
This generates an interactive UI:
Unfortunately this uses the default values specified in the component, and not the label hello
, which is what we wanted. To address this, we add an args
annotation to the story, which specifies the initial values:
export const Basic = (args) => <Button {...args} />;
Basic.args = { label: 'hello' };
Now we're back where we started, but we have a fully interactive story!
And this fully interactive story is also available in the Docs
tab of Storybook:
There are cases where you'd like to customize the controls that get auto-generated from your component.
Consider the following modification to the Button
we introduced above:
import React from 'react';
interface ButtonProps {
label?: string;
background?: string;
}
export const Button = ({ background, label = 'FIXME' }: ButtonProps) => (
<button style={{ backgroundColor: background }}>{label}</button>
);
And the slightly expanded story:
export const Basic = (args) => <Button {...args} />;
Basic.args = { label: 'hello', background: '#ff0' };
This generates the following Controls
UI:
This works as long as you type a valid string into the auto-generated text control, but it's certainly is not the best UI for picking a color.
We can specify which controls get used by declaring a custom ArgType
for the background
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 can be overridden by the user. Since background
is a property of the component, let's put that annotation on the default export.
import { Button } from './Button';
export default {
title: 'Button',
component: Button,
argTypes: {
background: { control: 'color' },
},
};
export const Basic = (args) => <Button {...args} />;
Basic.args = { label: 'hello', background: '#ff0' };
This generates the following UI, which is what we wanted in the first place:
NOTE:
@storybook/addon-docs
provide shorthand fortype
andcontrol
fields, so in the previous example,control: 'color'
is shorthandcontrol: { type: 'color' }
. Similarly,type: 'number'
can be written as shorthand fortype: { name: 'number' }
.
Up until now, we've only been using auto-generated controls based on the component we're writing stories for. What happens when we want a control for something that's not part of the story?
Consider the following story for our Button
from above:
import range from 'lodash/range';
// export default etc.
export const Reflow = ({ count, label, ...args }) => (
<>
{range(count).map((i) => (
<Button label={`${label} ${i}`} {...args} />
))}
</>
);
Reflow.args = { count: 3, label: 'reflow' };
This generates the following UI:
Storybook has inferred the control to be a numeric input based on the initial value of the count
arg. As we did above, we can also specify a custom control as we did above. Only this time since it's story specific we can do it at the story level:
// export const Reflow = ... (as above)
// Reflow.args = ...
Reflow.argTypes = {
count: { control: { type: 'range', min: 0, max: 20 } },
};
This generates the following UI with a custom range slider:
Note: If you set a component
for your stories, these argTypes
will always be added automatically. If you ONLY want to use custom argTypes
, don't set a component
. You can still show metadata about your component by adding it to subcomponents
.
To achieve this within an angular-cli build.
export const Reflow = ({ count, label, ...args }) => ({
props: {
label: label,
count: [...Array(count).keys()],
},
template: `<Button *ngFor="let i of count">{{label}} {{i}}</Button>`,
});
Reflow.args = { count: 3, label: 'reflow' };
Suppose you've created the Basic
story from above, but now we want to create a second story with a different state, such as how the button renders with the label is really long.
The simplest thing would be to create a second story:
export const VeryLongLabel = (args) => <Button {...args} />;
VeryLongLabel.args = { label: 'this is a very long string', background: '#ff0' };
This works, but it repeats code. What we want is to reuse the Basic
story, but with a different initial state. In Storybook we do this idiomatically for Args stories by refactoring the first story into a reusable story function and then .bind
ing it to create a duplicate object on which to hang args
:
const Template = (args) => <Button {...args} />;
export const Basic = Template.bind({});
Basic.args = { label: 'hello', background: '#ff0' };
export const VeryLongLabel = Template.bind({});
VeryLongLabel.args = { label: 'this is a very long string', background: '#ff0' };
We can even reuse initial args from other stories:
export const VeryLongLabel = Template.bind({});
VeryLongLabel.args = { ...Basic.args, label: 'this is a very long string' };
The controls addon can be configured in two ways:
As shown above in the custom control args and fully custom args sections, you can configure controls via a "control" annotation in the argTypes
field of either a component or story.
Here is the full list of available controls:
data type | control type | description | options |
---|---|---|---|
array | array | serialize array into a comma-separated string inside a textbox | separator |
boolean | boolean | checkbox input | - |
number | number | a numeric text box input | min, max, step |
range | a range slider input | min, max, step | |
object | object | json editor text input | - |
enum | radio | radio buttons input | options |
inline-radio | inline radio buttons input | options | |
check | multi-select checkbox input | options | |
inline-check | multi-select inline checkbox input | options | |
select | select dropdown input | options | |
multi-select | multi-select dropdown input | options | |
string | text | simple text input | - |
color | color picker input that assumes strings are color values | presetColors | |
date | date picker input | - |
Example customizing a control for an enum
data type (defaults to select
control type):
export default {
title: 'Widget',
component: Widget,
argTypes: {
loadingState: {
control: { type: 'inline-radio', options: ['loading', 'error', 'ready'] },
},
},
};
Example customizing a number
data type (defaults to number
control type):
export default {
title: 'Gizmo',
component: Gizmo,
argTypes: {
width: {
control: { type: 'range', min: 400, max: 1200, step: 50 },
},
},
};
Example customizing a color
data type:
export default {
title: 'Button',
component: Button,
argTypes: {
backgroundColor: {
control: { type: 'color', presetColors: ['#FFF', '#000', '#AAA'] },
},
},
};
Controls supports the following configuration parameters, either globally or on a per-story basis:
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
).
To enable expanded mode globally, add the following to .storybook/preview.js
:
export const parameters = {
controls: { expanded: true },
};
And here's what the resulting UI looks like:
If you don't plan to handle the control args inside your Story, you can remove the warning with:
Basic.parameters = {
controls: { hideNoControlsWarning: true },
};
Manual | Auto-generated | |
---|---|---|
React | + | + |
Vue | + | + |
Angular | + | + |
Ember | + | + |
Web components | + | + |
HTML | + | |
Svelte | + | |
Preact | + | |
Riot | + | |
Mithril | + | |
Marko | + |
Note: #
= WIP support
Addon-knobs is one of Storybook's most popular addons with over 1M weekly downloads, so we know lots of users will be affected by this change. Knobs is also a mature addon, with various options that are not available in addon-controls.
Therefore, rather than deprecating addon-knobs immediately, we will continue to release knobs with the Storybook core distribution until 7.0. This will give us time to improve Controls based on user feedback, and also give knobs users ample time to migrate.
If you are somehow tied to knobs or prefer the knobs interface, we are happy to take on maintainers for the knobs project. If this interests you, hop on our Discord.
If you're already using Storybook Knobs you should consider migrating to Controls.
You're probably using it for something that can be satisfied by one of the cases described above.
Let's walk through two examples: migrating knobs to auto-generated args and knobs to custom args.
First, let's consider a knobs version of a basic story that fills in the props for a component:
import { text } from '@storybook/addon-knobs';
import { Button } from './Button';
export const Basic = () => <Button label={text('Label', 'hello')} />;
This fills in the Button's label based on a knob, which is exactly the auto-generated use case above. So we can rewrite it using auto-generated args:
export const Basic = (args) => <Button {...args} />;
Basic.args = { label: 'hello' };
Similarly, we can also consider a story that uses knob inputs to change its behavior:
import range from 'lodash/range';
import { number, text } from '@storybook/addon-knobs';
export const Reflow = () => {
const count = number('Count', 10, { min: 0, max: 100, range: true });
const label = number('Label', 'reflow');
return (
<>
{range(count).map((i) => (
<Button label={`button ${i}`} />
))}
</>
);
};
And again, as above, this can be rewritten using fully custom args:
export const Reflow = ({ count, label, ...args }) => (
<>{range(count).map((i) => <Button label={`${label} ${i}` {...args}} />)}</>
);
Reflow.args = { count: 3, label: 'reflow' };
Reflow.argTypes = {
count: { control: { type: 'range', min: 0, max: 20 } }
};
There are a few known cases where controls can't be auto-generated:
With a little manual work you can still use controls in such cases. Consider the following example:
import { Button } from 'some-external-library';
export default {
title: 'Button',
argTypes: {
label: { control: 'text' },
borderWidth: { control: { type: 'number', min: 0, max: 10 }},
},
};
export const Basic = (args) => <Button {...args} />;
Basic.args = {
label: 'hello';
borderWidth: 1;
};
The argTypes
annotation (which can also be applied to individual stories if needed), gives Storybook the hints it needs to generate controls in these unsupported cases. See control annotations for a full list of control types.
It's also possible that your Storybook is misconfigured. If you think this might be the case, please search through Storybook's Github issues, and file a new issue if you don't find one that matches your use case.
The argTypes
annotation annotation can be used to hide controls for a particular row, or even hide rows.
Suppose you have a Button
component with borderWidth
and label
properties (auto-generated or otherwise) and you want to hide the borderWidth
row completely and disable controls for the label
row on a specific story. Here's how you'd do that:
import { Button } from 'button';
export default {
title: 'Button',
component: Button,
};
export const CustomControls = (args) => <Button {...args} />;
CustomControls.argTypes = {
borderWidth: { table: { disable: true } },
label: { control: { disable: true } },
};
Like story parameters, args
and argTypes
annotations are hierarchically merged, so story-level annotations overwrite component-level annotations.
MDX compiles to component story format (CSF) under the hood, so there's a direct mapping for every example above using the args
and argTypes
props.
Consider this example in CSF:
import { Button } from './Button';
export default {
title: 'Button',
component: Button,
argTypes: {
background: { control: 'color' },
},
};
const Template = (args) => <Button {...args} />;
export const Basic = Template.bind({});
Basic.args = { label: 'hello', background: '#ff0' };
Here's the MDX equivalent:
import { Meta, Story } from '@storybook/addon-docs/blocks';
import { Button } from './Button';
<Meta title="Button" component={Button} argTypes={{ background: { control: 'color' } }} />
export const Template = (args) => <Button {...args} />;
<Story name="Basic" args={{ label: 'hello', background: '#ff0' }}>
{Template.bind({})}
</Story>
For more info, see a full Controls example in MDX for Vue.
FAQs
Interact with component inputs dynamically in the Storybook UI
The npm package @storybook/addon-controls receives a total of 4,391,931 weekly downloads. As such, @storybook/addon-controls popularity was classified as popular.
We found that @storybook/addon-controls demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 11 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
The initial version of the Socket Python SDK is now on PyPI, enabling developers to more easily interact with the Socket REST API in Python projects.
Security News
Floating dependency ranges in npm can introduce instability and security risks into your project by allowing unverified or incompatible versions to be installed automatically, leading to unpredictable behavior and potential conflicts.
Security News
A new Rust RFC proposes "Trusted Publishing" for Crates.io, introducing short-lived access tokens via OIDC to improve security and reduce risks associated with long-lived API tokens.