<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kai Röder</title>
    <description>The latest articles on DEV Community by Kai Röder (@kairoeder).</description>
    <link>https://dev.to/kairoeder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F222194%2F3c627756-5714-4d7c-83cf-b3a07e286bba.png</url>
      <title>DEV Community: Kai Röder</title>
      <link>https://dev.to/kairoeder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kairoeder"/>
    <language>en</language>
    <item>
      <title>Storybook: The Workshop for Modern Frontends</title>
      <dc:creator>Kai Röder</dc:creator>
      <pubDate>Sun, 03 Nov 2024 20:43:49 +0000</pubDate>
      <link>https://dev.to/kairoeder/storybook-the-workshop-for-modern-frontends-1d6e</link>
      <guid>https://dev.to/kairoeder/storybook-the-workshop-for-modern-frontends-1d6e</guid>
      <description>&lt;p&gt;When developing an app, you usually depend on several things, such as a running backend, a user session, a specific user role, or test data.&lt;/p&gt;

&lt;p&gt;The problem is that the current app's state often does not reflect the state you need for your current task. Eventually, the more you add to your app, the harder it gets to reach certain states or pages. Worse, you might need to add workarounds to your code to force a specific state. Typical examples are switching between user roles or multi-step forms where every code change repeatedly forces you to complete previous steps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://storybook.js.org/" rel="noopener noreferrer"&gt;Storybook&lt;/a&gt; simplifies working on those hard-to-reach spots in your codebase by providing an isolated workspace. It is a separate framework-agnostic app within your repository. You can do everything from developing components to documenting all different component states and showcasing them nicely using &lt;a href="https://mdxjs.com/" rel="noopener noreferrer"&gt;MDX&lt;/a&gt; with clickable demos and interactively changeable component arguments. You can also test your components within Storybook. Storybook's add-on API allows you to tailor it to your needs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All examples use @storybook/angular v8 APIs but work similarly in other framework integrations such as React, Vue, Svelte, etc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Tell stories with your components
&lt;/h2&gt;

&lt;p&gt;The fundamental building block of Storybook is a "story", which is usually stored in a file ending with &lt;code&gt;*.stories.ts&lt;/code&gt;. A story represents a specific use case or state of a component.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Depending on the frontend framework, the file ending can be &lt;code&gt;*.stories.tsx&lt;/code&gt;, &lt;code&gt;*.stories.js&lt;/code&gt;, etc.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// `meta` describes the defaults for all stories within this file&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Meta&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ButtonComponent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Example/Buttons&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ButtonComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Default component input arguments&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Story&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;StoryObj&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ButtonComponent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Story&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Secondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Story&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above creates two new menu entries, &lt;code&gt;Example/Buttons/Primary&lt;/code&gt; and &lt;code&gt;Example/Buttons/Secondary&lt;/code&gt;, using the passed in &lt;code&gt;args&lt;/code&gt; to render the component.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft35lcz2ppfv1daa9b8nr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft35lcz2ppfv1daa9b8nr.png" alt="Storybook in the browser, showing the menu with entries for the " width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setting up stories with clickable components can be enough, but proper documentation for others in your project is also essential. Let's explore how to create docs pages using your existing stories!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up docs-pages
&lt;/h2&gt;

&lt;p&gt;A "Docs" page is either auto-generated and shows all stories of a &lt;code&gt;*.stories.ts&lt;/code&gt; file on one page or can be created manually and filled with custom content.&lt;/p&gt;

&lt;p&gt;To automatically generate it, set an additional property in your stories file's "meta" block.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Most "meta" level properties can also be set globally. Learn how to set up auto-generated docs globally &lt;a href="https://storybook.js.org/docs/writing-stories/tags#applying-tags" rel="noopener noreferrer"&gt;in the official Storybook documentation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Meta&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ButtonComponent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Example/Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ButtonComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// automatically generate docs-pages for stories within this file&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;autodocs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Out of the box, the auto-generated page contains all the stories, an additional "Show code" button to view the story code, and controls to change the component properties. Docs pages can help others in your project understand how to use a component and provide additional guidelines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://storybook.js.org/docs/writing-docs" rel="noopener noreferrer"&gt;See the official documentation&lt;/a&gt; to learn more about creating docs pages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpcpdw6p0rnmwbkzi6n05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpcpdw6p0rnmwbkzi6n05.png" alt="The image shows an auto-generated docs-page with the " width="800" height="649"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing within Storybook
&lt;/h2&gt;

&lt;p&gt;The whole testing topic deserves an entire post, but in 2024, it is also impossible to introduce Storybook without mentioning it.&lt;/p&gt;

&lt;p&gt;There are several ways to integrate Storybook into your test suite, such as &lt;a href="https://storybook.js.org/docs/writing-tests/import-stories-in-tests/stories-in-unit-tests" rel="noopener noreferrer"&gt;re-using story objects in your &lt;code&gt;*.spec.ts&lt;/code&gt; files&lt;/a&gt;, showing &lt;a href="https://storybook.js.org/docs/writing-tests/accessibility-testing" rel="noopener noreferrer"&gt;accessibility flaws&lt;/a&gt; directly in Storybook, or writing &lt;a href="https://storybook.js.org/docs/writing-stories/play-function" rel="noopener noreferrer"&gt;component and integration tests using "play" functions&lt;/a&gt;. I will briefly introduce the latter. &lt;/p&gt;

&lt;p&gt;A play function is part of a story object and uses &lt;a href="https://testing-library.com" rel="noopener noreferrer"&gt;Testing Library&lt;/a&gt; underneath. Play functions run directly in Storybook but &lt;a href="https://storybook.js.org/docs/writing-tests/test-runner" rel="noopener noreferrer"&gt;can also be run in CI&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@storybook/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// [...]&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Story&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;play&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button--hovered&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The screenshot below shows a failing test with the current and expected state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbnyz83jcmqp4brp5h1g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbnyz83jcmqp4brp5h1g.png" alt="The image shows a failing test inside the Storybook app" width="724" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me re-use the multi-step form example from the post introduction to give you a more advanced theoretical example. You could render the entire form and run through all the steps with different inputs to test your validators and other form logic, all while working on the form component in the same browser tab, which improves catching regressions early on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Use Storybook for projects at any scale. While it is easy to &lt;a href="https://storybook.js.org/docs/get-started/install" rel="noopener noreferrer"&gt;integrate Storybook into an existing project&lt;/a&gt;, in my opinion, new projects should use it right from the beginning.&lt;/p&gt;

&lt;p&gt;The fact that Storybook is decoupled from the backend and slices your requirements into multiple stories naturally forces you to make smarter component API decisions.&lt;/p&gt;

&lt;p&gt;Play functions are a perfect addition to the story-authoring format. With &lt;a href="https://testing-library.com" rel="noopener noreferrer"&gt;Testing Library&lt;/a&gt;, you can test your components from a user's perspective rather than querying DOM nodes, for example, by ID or CSS classes.&lt;/p&gt;

&lt;p&gt;The various options for showing stories in Storybook allow you to build your documentation for a specific audience. Depending on your requirements, you can use Storybook as a developer playground or showcase an entire design system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;To learn all about Storybook, a good starting point is &lt;a href="https://storybook.js.org/tutorials/" rel="noopener noreferrer"&gt;the tutorials section&lt;/a&gt; on the Storybook website.&lt;/li&gt;
&lt;li&gt;Create a Stackblitz demo with a framework of your choice at &lt;a href="https://storybook.new" rel="noopener noreferrer"&gt;storybook.new&lt;/a&gt; and play around with the examples right in the browser.&lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/yannbf"&gt;@yannbf&lt;/a&gt; from the Storybook team created &lt;a href="https://mealdrop.vercel.app/storybook/?path=/story/userflows-app--demo-mode&amp;amp;globals=viewport:responsive" rel="noopener noreferrer"&gt;Mealdrop&lt;/a&gt; as a showcase for a larger Storybook, using many add-ons. &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>angular</category>
      <category>storybook</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
