Let's assume that we want to wrap our Page stories so that in Storybook, they are inside a bordered container which is centered and has a max-width of 400px.
We can do that by changing the template function to look like this:
// src/stories/Page.stories.tsx
const Template: ComponentStory<typeof Page> = (args) =>
<div style={{ maxWidth: 400, margin: 'auto', border: '1px solid #fab' }}>
<Page {args} />
</div>
// ...rest of the code remains unchanged
And you should see the results in the stories:

However, it's not great to keep that in the template as you might want to reuse this code in other places as well. Storybook has a concept called Decorators, which allows us to better achieve this.
Component decorators#
Decorators are basically wrappers that receive a Story component and enhance it in a certain way. They are used to either provide visual enhancements or functionality (e.g. theming, state management, etc.).
We can extract the logic from the template and create a decorator from it:
// src/stories/Page.stories.tsx
// Create a new decorator - prefix of "with" + name of what it does
const withMaxWidth = (StoryFn) =>
<div style={{ maxWidth: 400, margin: 'auto', border: '1px solid #fab' }}>
<StoryFn/>
</div>
// .. rest of the code
// change Template back to its original version
const Template: ComponentStory<typeof Page> = (args) => <Page {args} />;
The decorator is an annotation that can be applied to every story if declared in the meta:
// stories/Page.stories.tsx
// ...other imports here
import { withMaxWidth } from '../../.storybook/decorators'
export default {
title: 'Example/Page',
component: Page,
decorators: [withMaxWidth], // add decorator
} as ComponentMeta<typeof Page>
// ...rest of the code remains unchanged
Things should work the same!
That's great! But what if we only want the decorator applied in one story of our component?
Story decorators#
Instead of passing the decorator to the meta like we did before, you pass it to the decorators
property of a story:
// stories/Page.stories.tsx
export default {
title: 'Example/Page',
component: Page,
// comment out or remove the decorator from meta
// decorators: [withMaxWidth],
} as ComponentMeta<typeof Page>
// ...rest of the code remains unchanged
export const LoggedOut = Template.bind({})
LoggedOut.decorators = [withMaxWidth]
That's it – now the decorator is only applied to the LoggedOut story. But what if we want the decorator to be applied in every single story of our project?
Let's first of all extract the code for our decorator. It's a good practice to gather the decorators that you want to share in your stories in a .storybook/decorators.tsx
file. Let's create one and add the following code:
// .storybook/decorators.tsx
import { DecoratorFn } from '@storybook/react'
// Receive a component and apply a bordered container with max width of 400px.
export const withMaxWidth: DecoratorFn = (Story) => (
<div style={{ maxWidth: 400, margin: 'auto', border: '1px solid #fab' }}>
<Story />
</div>
)
Global decorators#
To declare global decorators in Storybook, all you have to do is import the decorators you want to apply globally and export them in a named export called decorators
under the preview.js
file:
// .storybook/preview.js
import { withMaxWidth } from './decorators'
export const decorators = [withMaxWidth]
Now, all stories of all components will have a maxWidth container.
If we want to make the code more scalable, we can refactor the .storybook/decorators.tsx
file to already export an array of all decorators that should be globally applied to storybook, like so:
// .storybook/decorators.tsx
import { DecoratorFn } from '@storybook/react'
// export the decorator so it can be used individually
export const withMaxWidth: DecoratorFn = (StoryFn) => {
return <div style={{ maxWidth: 400, margin: 'auto', border: '1px solid #fab' }}>
<StoryFn />
</div>
}
// export an array of decorators that should be applied globally
export const globalDecorators = [
withMaxWidth
]
and then update the reference in the preview
file:
// .storybook/preview.js
import { globalDecorators } from './decorators'
export const decorators = globalDecorators
And there you go! We should be all set! This is a great way to organize code because preview.js
might contain a lot of other configuration and might become a bit too long. Keeping the decorators in its own file will make it simple for others to identify them, as well as understand which ones are applied globally by looking at the globalDecorators
export.
Because this exercise was mostly experimentational, let's not keep the decorator globally:
// .storybook/decorators.tsx
// ... rest of the code here
// export an array of decorators that should be applied globally
export const globalDecorators = [
// comment this out or remove it, but keep the rest of the code intact.
// withMaxWidth
]
Decorator inheritance#
In Storybook, the order of the decorators matter. This is because, before rendering a story, Storybook will run every decorator applicable to that story, in the following order:
Global decorators, in the order they are defined.
Component decorators, in the order they are defined.
Story decorators, in the order they are defined.
This means that a Story component will be wrapped by all of the decorators, from innermost to outermost. Given that, it's important to always pay attention to the order that you define your decorators in. The following pseudocode might help you visualize this:
// Given that these decorators:
export const decorators = [innermostDecorator, outermostDecorator]
// It would result in a story that renders like this:
const result = (
{/** OutermostDecorator ran second, wrapped the InnermostDecorator */}
<OutermostDecoratorLogic>
{/** InnermostDecorator ran first, wrapped the Story */}
<InnermostDecoratorLogic>
<Story />
</InnermostDecoratorLogic>
</OutermostDecoratorLogic>
)
What's next?#
In the next lesson, we will learn about Addons and explore the essential addons that come by default with Storybook.