The power of SDC comes from their ability to be self contained. If you have the need to build a complex component that displays data in a widget then building it as a SDC means that you can ensure that it looks and functions in the same way every time you include it.A popular way of running Storybook is to use DDEV as the wrapper. This makes sense since you won’t need to install Node on your local environment if you use DDEV.In addition, as the component is rendered using the theme that it is contained in you also get all of the theme files associated with that component. What this means is that any global styles set by the theme will be applied to the component and so you can assume that your component will look exactly like this when rendered fully within your theme.You should end up with a package.json file that looks like the following.composer require drupal/storybook
I used Storybook on a major project that had quite a few little widgets that needed styling in very particular ways. I built a collection of SDCs using Storybook in order to get the styling correct, and when they were dropped into the page they worked perfectly. It was quite rewarding, after hours of work getting the components to look good, to just add it into the site and see it working perfectly.
Creating A Single Directory Component
If you encounter a blank screen when attempting to view the story in Storybook then check your Drupal logs for more information. Sometimes the interaction with components through Storybook can cause errors that aren’t shown in Storybook, but Drupal will log the error.{
"name": "tests",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@storybook/addon-docs": "^9.1.8",
"@storybook/addon-webpack5-compiler-swc": "^4.0.1",
"@storybook/server-webpack5": "^9.1.8",
"storybook": "^9.1.8"
}
}
drush storybook:generate-stories themes/custom/my_theme/components/author/author.stories.twig
In this article we will look at creating an SDC in Drupal and then using Storybook to create and preview that component.aside.author {
clear: both;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 10%), 0 4px 6px -2px rgba(0, 0, 0, 10%);
padding: 1rem;
margin-bottom: 2rem;
margin-top: 2rem;
display: flex;
aside.author:first-child {
width: 20%;
text-align: center;
img {
max-width: 100%;
height: auto;
}
}
aside.author:last-child {
width: 80%;
}
div.author_bio {
margin-left: 1rem;
p {
margin-top: 0;
}
}
}
Different SDC can be nested together, which means that a site can be built up from different components working together to generate the content.ddev drush storybook:generate-all-stories --uri=https://drupaldev.ddev.site/
In the following example, we are adding a second example to the story to show the component with a much longer title and as part of a list element (just to show the component within different markup as a silly example).
The Storybook Module
Before you can use Storybook you need to allow Storybook to communicate with your Drupal site. If you don’t do this step then you’ll see CORS errors in the console of your Storybook site, which will prevent it from working correctly. Your Storybook site will just show a blank screen for the component or it might even show an error message from Storybook.name: Author
description: "Display author information"
props:
type: object
properties:
author_url:
type: string
title: Author URL
examples:
- /author/philipnorton42
slots:
name:
title: "Name"
bio:
title: "Bio"
avatar:
title: "Avatar"
You should be building SDC in isolation, but if you do find an odd interaction between two components then you can create a story that mimics this interaction and address any issues. Which gives a nice basis for visual regression tests.Finally, we can run Storybook and look at the results of our work.With the module setup and the components configures we can now look at installing Storybook.To preview an SDC in Storybook we first need to create one, this will be used as an example thoughout the rest of the article. It is assumed that you have a custom theme that you can build an SDC in.drush role:perm:add anonymous 'render storybook stories'
The module documentation for the Drupal Storybook module talks about using yarn to install Storybook, but I’ll be looking at using npm to install and run Storybook in this article.Storybook is a separate application, and so does need updating and maintaining just like any other dependency. By installing it in its own directory, however, we prevent it from adding its footprint to our Drupal site. The Storybook application is being maintained by the developers, so it’s not like this is some old application that will be going away soon. At the time of writing this the last Storybook release is 9.1.8, which was released just 5 days ago. I think the benefits of maintaining this application outweigh the time spent creating stories in your components.This isn’t the only way to preview SDC in Drupal, so join me next time where we will be looking at previewing SDC using the SDC – Component library module.Single Directory Components (SDC) consist of a directory of files that go together to create a small component on a Drupal website. The component contains all the templates, styles, script, and images needed to display some content in a consistent way.ddev exec "cd tests && npm run build-storybook"
First, let’s make an SDC that we can use as an example with this application.watch --color drush storybook:generate-all-stories
There can be issues around Storybook communicating correctly with Drupal if run like this. With the biggest issue being mixed content due to http and https being used at the same time. If you are having problems then try running the storybook:generate-all-stories
command with a --uri
flag to force the correct domain and protocol.<aside class="author">
<div>
{% block avatar %}
{{ avatar }}
{% endblock %}
</div>
<div class="author_bio">
<p>
<a href="{{ author_url }}" rel="bookmark">
<span>{{ name }}</span>
</a>
</p>
{% block bio %}
{{ bio }}
{% endblock %}
</div>
</aside>
To build the Storybook files run the following command in your tests directory. Once you have the stories in place you can then run a Drush command to generate all of the stories. Storybook can’t make use of the <component name>.stories.twig
files itself and needs a <component name>.stories.json
file to link it to Drupal. This file just contains information about the arguments present, but the main <component name>.stories.twig
file is still used to render templates.web_extra_exposed_ports:
- name: storybook
container_port: 6006
http_port: 6007
https_port: 6006
web_extra_daemons:
- name: node.js
command: "tail -F package.json > /dev/null"
directory: /var/www/html/tests
{% embed 'my_theme:componentName' with {another_property: 'Another value'} %}
{% endembed %}
{% stories componentName with { title: 'Components/ComponentName' } %}
{% story default with {
name: '1. Default',
args: {
some_property: 'A value'
}
} %}
{% embed 'my_theme:componentName' %}
{% endembed %}
{% endstory %}
{% endstories %}
Your development workflow should be to build the skeleton of the component and create a story for that component right away. You can then flesh out the component in Storybook and then add it to your theme when it is ready for use.In the main author.twig
file we create some simple markup and add in the attributes defined in the author.component.yml
file. The blocks are important here, but they will only become useful when we setup the stories for Storybook which we will cover later in the article.Finally, the Storybook module comes with a permission called render storybook stories
that is required to render the stories. The following command will activate this permission for anonymous users. We won’t do a deep dive into SDC here since it can be a large subject, so we’ll just create the needed elements. If you want more information then the official Drupal documentation on SDC is actually very good. There is also a Drupal 10 Theme Development book that has a comprehensive guide on building and using SDC in Drupal.
Installing Storybook
Now that we have previewed the Author component inside Storybook and checked that it works correctly we can add it to our theme. The author component I created at the start is now being used by this theme in the following way (inside a Author minimal view mode template). {% story default with {
name: '1. Default',
args: {
some_property: 'A value'
}
} %}
{{ include('my_theme:componentName', {
some_property
}) }}
{% endstory %}
{% stories componentName with { title: 'Components/ComponentName' } %}
{% story default with {
name: '1. Default',
args: {
some_property: 'A value'
}
} %}
{% embed 'my_theme:componentName' %}
{% endstory %}
{% endstory %}
{% story wrappedComponent with {
name: '2. Wrapped Component',
args: {
some_property: 'A slightly longer title that might cause the design to break if our styles are not correct.'
}
} %}
<ul>
<li>
{% embed 'my_theme:componentName' %}
{% endembed %}
</li>
</ul>
{% endstory %}
{% endstories %}
drush storybook:generate-all-stories
"stories": [
"../../web/themes/custom/my_theme/components/**/*.stories.@(json|yaml|yml)"
],
If you are using DDEV and want to use this method then you can run the build command in the following way.Storybook is a standalone application that will send requests to Drupal to render the components that you have configured with stories files. Whilst it is possible to install Storybook in the root of your Drupal project I strongly suggest that you give Storybook its own directory to exist in. This prevents Storybook becoming a hard dependency of your project and interfering with other packages like eslint that you might actually want installed in the root of your project for coding standards purposes.The first step to get them working together is to require the Storybook module.
CORS mitigation
drush state:set twig_debug 1
drush state:set twig_cache_disable 1
drush state:set disable_rendered_output_cache_bins 1
npm run build-storybook
npm run storybook
{{ include('hashbangcode_theme:author', {
author_url: url,
name: label,
bio: content.field_author_short_bio,
avatar: content.field_author_avatar
}) }}
/** @type { import('@storybook/server-webpack5').Preview } */
const preview = {
parameters: {
server: {
url: `https://<your ddev site>.ddev.site/storybook/stories/render`,
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;
"scripts": {
"storybook": "storybook dev -p 6006 --no-open",
"build-storybook": "storybook build"
},
ddev exec "cd tests && npm run storybook"
I have also used Storybook to preview components to clients. In fact, more than one demo has been just showing the component inside Storybook and making changes to the component before it was added to Drupal. By hosting Storybook next to your development site you also allow clients to do the same with their internal shareholders.args
section of the story are passed through to the Storybook application and this allows users to tweak those arguments within the Storybook interface.Most of the problems with Storybook comes from getting Storybook and Drupal to play nicely together. CORS errors and mixed content mode are probably the most common thing that causes problems with Storybook and Drupal. I have also found that Storybook will sometimes heavily cache the output, especially in the right menu bar, and so getting updates to your components can sometimes be a bit of a pain.The arguments we stipulated in the <component name>.story.twig
file are presented as options in the Storybook interface below the preview window. This allows us to change the values in the component to see how different parameters and options change how the component reacts. By doing this we can test the component with different content or settings without having to refresh the page.
Running Storybook In DDEV
location ^~ /storybook-static {
add_header "Access-Control-Allow-Origin" *;
add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS";
disable_symlinks off;
alias /your/project/directory/tests/storybook-static;
index index.html;
}
Open the file called main.js
in the newly created .storybook
directory and make sure that the "stories"
configuration item looks like this.We can generate the story for a single component using the storybook:generate-stories
command, giving it a path to the component (relative to the Drupal web root).Instead of running Storybook as an application we can build the files needed for storybook and serve them as a separate website.If you have a theme that contains components already but aren’t using Storybook then you should absolutely look at using this system. Adding stories for the components isn’t too much of a task, and can only take 10-15 minutes per component.You can watch for changes in your Twig files and run this command automatically using the following.The full stories file for the author component looks like this.There are a couple of ways to do this, but let’s first focus on running the application locally. Assuming you have Node installed you can change directory to the tests
directory (or wherever you installed Storybook) and run the following command.The author.component.yml
file defines an author URL as a property and a a few slots to pass in the name, bio, and avatar information.
Building And Hosting Storybook
For this example I will create a simple author component that will display the name, bio, and avatar of the author of an article. Here are the needed files for this component to function. location ^~ /storybook-static {
add_header "Access-Control-Allow-Origin" *;
add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS";
disable_symlinks off;
alias /var/www/html/tests/storybook-static;
index index.html;
}
$settings['container_yamls'][] = DRUPAL_ROOT . '/sites/development.services.yml';
For our author component we created a file called author.stories.twig
, and because we have HTML to pass to the template we need to use the embed
Twig tag. This is where the use of the Twig block
tags comes into play. In order to transmit the values in the args
section of the story to the template we need to override the blocks and inject the args in a raw form to the template. This allows us to use HTML in the Storybook arguments without having to add raw output to the template itself, which can be insecure. It also allows the args to be used to in the Storybook interface.
Hosting Storybook In DDEV
Create a directory called something like storybook
or tests
; it just needs to be somewhere for Storybook to live. Then, install Storybook using the following commands.To bypass the CORS rules you need to add the following parameters to your development.services.yml
file in your Drupal site.First, edit your .ddev/config file and add the following lines of config. This tells DDEV to open ports 6006 and 6007 for the use of Storybook, and that the node.js daemon should watch the package.json file; which keeps the docker container alive as long as we need it.
Conclusion
You can optionally pass variables to the embed tag using the with
attribute. Any variables that we create using the args attribute are automatically available to the templates inside the embed statement, but this is a useful way of adding additional (static) overrides to the template.parameters:
storybook.development: true
cors.config:
enabled: true
allowedHeaders: [ '*' ]
allowedMethods: [ '*' ]
allowedOrigins: [ '*' ]
exposedHeaders: false
maxAge: false
supportsCredentials: true
You should be presented by a preview of the story that we configured a while ago.npm init -y
npx storybook init --type server
drush pm:install storybook
In order to get Storybook to run in DDEV, however, you need to change a couple of things.{% stories author with { title: 'Components/Author' } %}
{% story default with {
name: '1. Default',
args: {
author_url: '/author/philipnorton42',
name: 'Phil Norton',
bio: '<p>Phil is the founder and administrator of <a href="https://www.hashbangcode.com">#! code</a> and is an IT professional working in the North West of the UK.</p>',
avatar: '<img loading="lazy" src="https://picsum.photos/id/237/480/480" width="480" height="480" alt="Test image" class="image-style-medium">'
}
} %}
{% embed 'my_theme:author' %}
{% block avatar %}
{{ avatar|raw }}
{% endblock %}
{% block bio %}
{{ bio|raw }}
{% endblock %}
{% endembed %}
{% endstory %}
{% endstories %}
A typical stories file would have the following basic structure.The connection between Drupal and Storybook is provided by the Drupal Storybook module. This modules provides the needed Drush commands that we can use to create the files needed for Storybook, and also has a number of endpoints that allow Storybook to pick up and render the components.This works because Storybook creates an iframe that points to the Storybook module’s endpoint inside Drupal. Doing this allows Drupal to return the markup for the component along with any CSS and JavaScript that is required for the component to work.