🔖 Storybook + Angular: Writing Stories for Your UI Components

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.

Leave a Comment

Your email address will not be published. Required fields are marked *