
If you’re building a design system or reusable UI components in Angular, Storybook is a game-changer. It lets you work on components in isolation, test them independently, and visually document all the variations. In this post, I’ll walk through how I set up stories for a simple Angular component — the TagComponent
.
🧩 The Component: TagComponent
Let’s say we’ve got a basic TagComponent
that supports optional removal behavior. Here’s what it looks like:
tag.component.html
<div class="tag-container" [class.removable]="removable">
<output><ng-content></ng-content></output>
<span *ngIf="removable">
<i class="fa-regular fa-times icon" (click)="remove()"></i>
</span>
</div>
tag.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-tag',
templateUrl: './tag.component.html',
styleUrls: ['./tag.component.scss']
})
export class TagComponent {
@Input() removable = false;
remove() {
console.log('Tag removed');
}
}
📝 Setting Up the Story: tag.stories.ts
Now let’s write a Storybook file that shows off the different states and styles of this component.
tag.stories.ts
import { moduleMetadata, type Meta, type StoryFn } from '@storybook/angular';
import { TagComponent } from './tag.component';
import { BrComponent } from '@app/shared/components/br/br.component'; // optional spacing component
const meta: Meta<TagComponent> = {
title: 'Shared Components/Tag',
component: TagComponent,
decorators: [
moduleMetadata({
imports: [],
declarations: [BrComponent], // if you're using <br /> as a component
}),
],
};
export default meta;
type Story = StoryFn<TagComponent>;
// Simple default tag
export const Default: Story = () => ({
template: `<app-tag>Placeholder</app-tag>`,
});
// Removable tag (with close icon)
export const Removable: Story = () => ({
template: `
<app-tag [removable]="true">Placeholder</app-tag>
<br />
<app-tag class="dark" [removable]="true">Placeholder</app-tag>
`,
});
// Dark theme tag
export const Dark: Story = () => ({
template: `<app-tag class="dark">Placeholder</app-tag>`,
});
// Smaller tag variants
export const Small: Story = () => ({
template: `
<app-tag class="dark small">Placeholder</app-tag>
<br />
<app-tag class="small">Placeholder</app-tag>
`,
});
// Even smaller (mini) tags
export const Mini: Story = () => ({
template: `
<app-tag class="dark mini">Placeholder</app-tag>
<br />
<app-tag class="mini">Placeholder</app-tag>
`,
});
// Hover interaction across full tag area
export const HoverOnEntireTag: Story = () => ({
template: `
<app-tag class="dark hover-entire-tag">Placeholder</app-tag>
<br />
<app-tag class="dark hover-entire-tag" [removable]="true">Placeholder</app-tag>
`,
});
🛠 How This Works
- We define a Meta config that links our component and any dependencies like
BrComponent
. - Each story returns an Angular template that showcases a different version of
TagComponent
. - We use custom CSS classes to style the tag as
dark
,mini
,small
, etc. - You can easily extend this by binding
args
to control@Input()
s directly through the Storybook UI.
✅ Why Use Storybook?
Here’s why this workflow has been a game-changer for my team:
- Visual feedback: See UI changes immediately without running the full app.
- Design-system friendly: Document all component states in one place.
- Efficient debugging: Work on components in isolation without routing or layout noise.
- Great for handoff: Designers and QA can view and test components directly in Storybook.