This post compares the DocsPage and MDX syntax to write story of your UI components using Storybook. We will continue with the Card component we talked about in my last article.
There are two ways you can write stories using Storybook: DocsPage
and MDX
. Both, DocsPage and MDX are made possible by the Storybook addon called Docs.
DocsPage is a default way of writing component stories with zero configuration where you simply create *.stories.js
file to get started. I demonstrated how can use different options to document a Card component and organise text description, code samples, control panels options in the previous article: Create Card component story using Storybook with Nuxt
MDX gives you lot of freedom to create free-form pages for each component, where we can simultaneously document components and write stories by creating *.stories.mdx
file. I've configured my Nuxt project for the Storybook and to use MDX syntax, I have let Storybook know that I wish to use .mdx
files for my stories. We can do this by updating our Storybook configuration in nuxt.config.js
// nuxt.config.js
export default {
// ...
storybook: {
// ...
stories: ["~/components/**/*.stories.mdx"],
},
};
Let's recap how the story is made. A story is a function that describes how to render different states of your UI components. There three steps as I see it:
The default export metadata controls how Storybook lists your stories.
<!-- card.stories.mdx -->
import { Meta } from '@storybook/addon-docs/blocks';
<meta title="MDX/Card" component="{Card}" />
Named export is used to define component’s story using Template
.
<!-- card.stories.mdx -->
import { Meta, Story } from '@storybook/addon-docs/blocks';
<meta title="MDX/Card" component="{Card}" />
<!-- Define template for Primary Story -->
export const PrimaryTemplate = () => ({})
<!-- named export Primary to create respective story -->
<Story name="Primary">
{ PrimaryTemplate.bind({})}
</Story>
When using MDX syntax, we will have to make use of <Meta />
and <Story />
components which we can import from Storybook addon-docs/block.
import { Meta, Story } from '@storybook/addon-docs/blocks';
<Meta />
component helps us describe the component and <Story />
component helps us describe the story as show in the code above.👆
We can also wrap the <Story />
into a <Canvas />
component for extra features, i.e get automatically generated source code snippet. In DocsPage, every story is wrapped in a Canvas block by default where we cannot opt out of displaying it. On the other hand, using MDX convention, we can choose to drop this Canvas wrapper component if we want to.
<Canvas>
<Story name="Primary">
{ PrimaryTemplate.bind({})}
</Story>
<Canvas/>
As an example, I will try to replicate all three stories using MDX: Primary, SVG and Gradient.
The story-outline for above scenario would look something like this.👇
<!-- card.stories.mdx -->
import { Meta, Story } from '@storybook/addon-docs/blocks';
<meta title="MDX/Card" component="{Card}" />
<!-- Primary Story -->
<!-- Define template for Primary Story -->
export const PrimaryTemplate = () => ({})
<!-- named export Primary to create respective story -->
<Story name="Primary">
{ PrimaryTemplate.bind({})}
</Story>
<!-- SVG Story -->
<!-- Define template for SVG Story -->
export const SVGTemplate = () => ({})
<!-- named export SVG to create respective story -->
<Story name="SVG">
{ SVGTemplate.bind({})}
</Story>
<!-- Gradient Story -->
<!-- Define template for Gradient Story -->
export const GradientTemplate = () => ({})
<!-- named export Gradient to create respective story -->
<Story name="Gradient">
{ PrimaryTemplate.bind({})}
</Story>
args
and argTypes
Although our Card component has default values for its props
, on initial render of the component in Storybook, it shouldn't use default values set for the props, but it should rather have:
padding
of 3
primary-color
set to green-400
andborder-radius
to 3xl
...given that all three props are are available on the component.
In this case, when we export our story, we can set the args
values using Primary.arg = {}
👇
<!-- card.stories.mdx -->
<!-- args at component-level -->
<Meta title="MDX/Card"
component={Card}
args={{
padding: 3,
primaryColor: "green-400",
borderRadius: "3xl"
}}/>
<!-- args at story-level -->
<Story name="Primary"
args={{
padding: 3,
primaryColor: "green-400",
borderRadius: "3xl"
}}
>
{ PrimaryTemplate.bind({}) }
</Story>
And finally, padding
, primaryColor
and borderRadius
variables should be bound to the story template to take effect and for further dynamic manipulation.
<!-- card.stories.mdx -->
export const PrimaryTemplate = (args, { argTypes }) => ({ props:
Object.keys(argTypes), template: `
<card
:padding="padding"
:primary-color="primaryColor"
:border-radius="borderRadius"
>
<!-- ... -->
</card>
`, });
Now, let's say we wish to limit the padding
values available for our component. In this case, we will go one step back from the story-level, and onto the component-level and use argTypes
option.
<!-- card.stories.mdx -->
<!-- argTypes at component-level -->
<Meta title="MDX/Card" component={Card} argTypes={config.argtypes}/>
<!-- argTypes at story-level -->
<Story name="Primary"
argTypes = {
borderRadius: {
control: {
type: "select",
options: ["2xl", "3xl", "lg", "md"]
},
defaultValue: "2xl"
}
}
>
{ PrimaryTemplate.bind({})}
</Story>
Now that we have argTypes
, we can come down at the story-level and pass it into the story template props
. 👇
<!-- card.stories.mdx -->
export const PrimaryTemplate = (args, { argTypes }) => ({ props:
Object.keys(argTypes),
<!-- ... -->
})
Addons helps us extend Storybook's functionalities. There are different types of addons available for Storybook and you can see the full list of addons here.
Out of the box, component is rendered on the left-hand side of the Canvas and the Docs page. But let's say we want to centre-aligned our component for all stories.
Since we want to apply this behaviour to all stories of a component, it makes sense to apply this change at component-level.👇
// card.stories.js
// decorators at component level
export default {
title: "JS/Card",
component: Card,
decorators: [
() => ({
template:
'<div style="display: flex; align-items: center; justify-content: center;"><story /></div>',
}),
],
};
In case, we needed to apply completely different markup for each stories, then we would apply decorators
at story-level like below.
<!-- card.stories.mdx -->
<!-- decorators at component-level -->
<Meta title="MDX/Card"
component={Card}
argTypes={config.argtypes}
decorators=[()=>({template: '<div style="display: flex; align-items: center; justify-content: center;"><story /></div>'})]
/>
<!-- decorators at story-level -->
<Story name="Primary"
decorators={[() => ({ template: '<div style="padding: 3rem;"><story /></div>' })]}
>
{ PrimaryTemplate.bind({})}
</Story>
We want to add custom text on the doc section of the Storybook for our Card
component that should look something like this.👇
In MDX, we are free to combine text blocks and components, so it may be limiting to use docs.description.component
option to add content, because in MDX you can add content as if you are writing any MarkDown file.But this also doesn't mean we can't apply other parameters
to MDX based story. See how we can control the layout of a story using parameters
.
<!-- card.stories.mdx -->
<!-- parameter at component-level -->
<Meta title="MDX/Card"
component={Card}
parameters={{ layout: "centered" }}
/>
<!-- parameter at story-level -->
<Story name="Primary"
parameters={{ layout: "centered" }}
>
{ PrimaryTemplate.bind({})}
</Story>
One of the fundamental element of documentation is the code samples. With Storybook, we can display code samples in couple of ways.
<!-- card.stories.mdx -->
import { Meta, Story, Props, Source } from "@storybook/addon-docs/blocks";
<!-- Use source tag in mdx to provide the code sample at story level -->
<Source
language="html"
code={dedent`
<card
:padding="3"
:primary-color="pink-400"
secondary-color="gray-300"
:border-width="2"
:border-radius="xlg"
class="w-72"
>
<!-- ... -->
</card>
`}
/>;
I have barely scratch the surface of what Storybook has to offer but seeing how Storybook is not opinionated and what's possible with MDX, I'm encouraged to try to create completely custom layouts to document my UI components and examine how far I can go.
Before I finished, let's recap the key concepts of Storybook I covered in this article.
args
args
can be applied at story and component level.args
are dynamic data that are provided and updated by Storybook so that you can see your UI component in action and manipulate it dynamically.args
can be used to dynamically change props, slots, styles, inputs, etc. This allows Storybook and its addons to live edit components.argTypes
argType
can also be applied at story and component level.argTypes
specify the behaviour of Args and help you constrain the values that args
can accept.parameters
parameters
can be applied at story, component and globally.parameters
help you control the behaviour of Storybook features and addons.decorators
decorators
can be applied at story, component and globally.decorators
helps you control how your story is rendered. Most typical use-case of decorators is to wrap stories with additional HTML markup.I hope you enjoyed reading this article. For more demos and articles on Nuxt and Vue, you can follow me on Twitter @KrutiePatel.