Given the components identified in the previous lesson, let's create the most atomic ones: The Badge
and the Review
.

The Badge component#
The Badge is a pretty straightforward component and doesn't contain any variation.
You will find a base template for the component already done for you under src/components/Badge/Badge.tsx
:
// src/components/Badge/Badge.tsx
type BadgeProps = {
text: string
className?: string
}
export const Badge = ({ text }: BadgeProps) => (
<div>
{text}
</div>
)
Now it's time to create a stories file for this component! In a previous lesson, we mentioned that the preferred way of organizing files is via colocation, which is the way we will be organizing our files in this course.
Let's create the file Badge.stories.tsx
in the same folder as the component and add a base template to it as well:
// src/components/Badge/Badge.stories.tsx
import { ComponentStory, ComponentMeta } from '@storybook/react'
import { Badge } from './Badge'
// Metadata of our component
export default {
title: 'Components/Badge',
component: Badge,
} as ComponentMeta<typeof Badge>
// Base Template
const Template: ComponentStory<typeof Badge> = (args) => <Badge {args} />
// Story, a component variation consisting of a template + args
export const Default = Template.bind({})
Default.args = {
text: 'Comfort food',
}
You should be able to see the component in Storybook. If you notice, in the addons panel, the controls addon provides the interface for you to tweak with the component properties (in this case, the text), and you can play with it without having to update your code!

Important
You can also toggle the addons panel by pressing
A
in your keyboard.
Now let's add some styles. let's add styled-components and wrap the text in a colored container:
// src/components/Badge/Badge.tsx
import styled, { css } from 'styled-components'
const Container = styled.div(
() => css`
padding: 3px 8px;
background: #e9e9e9;
border-radius: 4px;
display: inline-block;
span {
color: #636363;
}
span:first-letter {
text-transform: capitalize;
}
`
)
type BadgeProps = {
text: string
// We use classname to make the component extensible by styled-components
className?: string
}
export const Badge = ({ text, className }: BadgeProps) => (
<Container className={className}>
<span>
{text}
</span>
</Container>
)
That's it. We have done our first component, directly on Storybook!

The key here is that we didn't have to run the app, and we focused solely on developing the Badge component in isolation. Now this example might be simple, but imagine that the badge component lives on a page that is difficult to access, for example inside the checkout page, only visible for users with a premium membership. By using Storybook and being able to focus on the component itself, we definitely cut lots of time and hassle. Additionally, we also directly make documentation of the component and others will know that the component is reusable.
Now let's do another one: the Review component.
The Review component#
Just like the Badge component, we start with a base template for the Review component, under src/components/Review/Review.tsx
:
// src/components/Review/Review.tsx
type ReviewProps = {
rating?: number
}
export const Review = ({ rating }: ReviewProps) => <div>Hello world!</div>
Let's create the Review.stories.tsx
file in the same folder as the component, with some base template:
// src/components/Review/Review.stories.tsx
import { ComponentStory, ComponentMeta } from '@storybook/react'
import { Review } from './Review'
export default {
title: 'Components/Review',
component: Review,
} as ComponentMeta<typeof Review>
const Template: ComponentStory<typeof Review> = (args) => <Review {args} />
export const Default = Template.bind({})
When checking Storybook, we see the component appear:

Adding the business rules#
A "Hello world" however is not really what we want though! The review component should have a business rule that states that given a score, it should present the following texts:
If it doesn't have a rating: No reviews yet
5: ★ Excellent
Between 4 and 5: ★ Very good
Between 2 and 4: ★ Adequate
Lower than 2: ★ Very poor
Let's add that logic to the component:
// src/components/Review/Review.tsx
type ReviewProps = {
rating?: number
}
const getReview = (rating?: number) => {
if (!rating) {
return 'No reviews yet'
}
let reviewText = 'Very poor'
if (rating >= 2 && rating < 4) {
reviewText = 'Adequate'
} else if (rating >= 4 && rating < 5) {
reviewText = 'Very good'
} else if (rating >= 5) {
reviewText = 'Excellent'
}
return `★ ${rating.toFixed(1)} ${reviewText}`
}
export const Review = ({ rating }: ReviewProps) => <div>{getReview(rating)}</div>
If you notice the controls panel, the addon already analyzed the rating
arg and inferred it as a number input so that we can play with it, already seeing our component respond to different ratings:

Working with argTypes#
However, we can improve the control UI by defining custom argTypes. ArgTypes let Storybook know exactly the type you want to be used for your args, which affects addon controls and docs, displaying a different UI based on what we want.
By default, Storybook will try to infer the argTypes automatically based on metadata extracted from the component type passed in ComponentStory
. If that type was not there, Storybook wouldn't be able to figure that out!
However, argTypes can also be manually specified. In our case, it would be great to present a nice slider, with values like minimum of 0, maximum of 5, and a step of 0.1. We can achieve that by defining the argType for the rating
args to be of type range:
// src/components/Review.stories.tsx
import { ComponentStory, ComponentMeta } from '@storybook/react'
import { Review } from './Review'
export default {
title: 'Components/Review',
component: Review,
// define argTypes parameter on meta
argTypes: {
// set the arg name that you want to override
rating: {
// add custom control configuration
control: {
type: 'range',
min: 0,
max: 5,
step: 0.1,
},
},
},
} as ComponentMeta<typeof Review>
const Template: ComponentStory<typeof Review> = (args) => <Review {args} />
export const Default = Template.bind({})
Which yields a better experience when using the controls panel:

Now, it's much more interesting to play around with the component's property, given that the values are always related to the use cases. You can find more about argTypes and the available types here.
Controls panel vs defining multiple stories#
You might now be asking: If I can reproduce every use case of this component by using the controls panel, should I even create more stories?
Even though we technically can see every use case by playing with the controls, it's important to document the use cases as stories for our component, so anyone accessing our Storybook can easily identify them without having to figure them out from the controls panel.
Every story should reflect an important use case of a component. The Review component has rules for ratings, so let's add a story for every use case:
// src/components/Review.tsx
// ...previous code
export const Default = Template.bind({})
export const Excellent = Template.bind({})
Excellent.args = {
rating: 5,
}
export const VeryGood = Template.bind({})
VeryGood.args = {
rating: 4.3,
}
export const Adequate = Template.bind({})
Adequate.args = {
rating: 2.5,
}
export const VeryPoor = Template.bind({})
VeryPoor.args = {
rating: 1,
}
Great! Now anyone with access to Storybook will quickly see all the scenarios of the Review component. New colleagues in your team don't necessarily have to jump into the code now to get to know that info anymore. How cool is that?

What's next?#
We have now successfully written stories for the Badge
and Review
components! However, their styles are not really correct. Normally, apps have global styles to be applied across every component, and MealDrop is no different.
In Storybook, these styles are missing! That's why we see that the fonts in those components is incorrect.
In the next lesson, we will learn how to apply global styles to Storybook by adding decorators to provide theming support.