Single directory components (SDC) is the biggest paradigm shift in Drupal since the introduction of Twig in 2013! At its core, SDC allows you to gather related files from a component (CSS, JS, Twig), and group them into one directory. SDC will automatically generate the library and load it when the component is called.
Because we define the schema within a component’s YAML definition, modules can automatically create forms to populate data. This means that in the future, modules will automatically detect components and insert their respective forms into Layout Builder, CKEditor, Paragraphs, Blocks, Fields, etc! In addition, you’ll be able to integrate your SDC components directly into Storybook or other component library managers!
Background on Single Directory Components (SDC)
Mateu, Lauri, and I started discussing SDC in October 2022. The vision was heavily inspired by Mateu’s work on the CL Components suite of modules, which provides similar base functionality (and much more).
Since then, Mateu has been writing the code for the module, and discussing architectural decisions with Lauri, me, and other developers. Mateu and I also met with the maintainers of UI Patterns to validate the architecture and make sure it will work for them.
During a core committer meeting, it was determined that the best way to bring this into core is as an experimental module. This will allow SDC to validate and modify its architecture as necessary as more developers develop against it and extend it. Our hope is to get SDC committed to Drupal core in time for the 10.1.0 release in June 2023.
Relevant Drupal.org issues for SDC
- Initial proposal issue to bring SDC into core
- Roadmap issue for single directory components
- Add Single Directory Components as a new experimental module
- Create new tabs component for Olivero using SDC
For a more detailed background of SDC, see Mateu’s article, Getting Single Directory Components in Drupal Core.
Lets migrate the “tabs” Olivero component to SDC!
As part of the roadmap issue, we want to validate the architecture by creating SDC components within both Olivero and Umami (the demo theme). I chose to migrate Olivero’s tabs component because it has CSS, JavaScript, and several variables to be passed into it.
Moving this component into SDC was easier than I expected, and took about 10 minutes. This makes me even more excited about getting this into core!
Step 1: Prepare core by applying SDC patch and setting up Twig debugging.
Drupal core obviously doesn’t yet have SDC available, so we need to apply the patch. To do this, I pulled down the plain diff from the open merge request, and applied it using git apply
. Now when I navigate to the modules page, I see Single Directory Components, and can enable it.
Because I’m doing theme development, I also want to enable Twig debugging, as well as enable verbose error logging. This will make sure Drupal outputs errors on the screen, instead of giving a “White screen of death” (WSOD).
$config['system.logging']['error_level'] = 'verbose';
Step 2: Create the directory and files
For SDC to recognize our component, we need to create a components
directory within the theme, and then create a subdirectory with the component’s name (in this case the name will be “tabs”).
Within this new components/tabs
directory, we’ll create two files
tabs.component.yml
- This will hold the metadata for the component (friendly name, status, description, etc), as well as the schema for the data that the component will use.tabs.twig
- This will hold the markup for the component
Note that Mateu’s CL Generator module provides a Drush command to scaffold the components. It’s also likely that Drush will eventually have built-in support for this once SDC is in Drupal core.
Step 3: Populate the tabs.twig
The final tabs.twig
file will look like this:
{% if primary %}
<h2 id="primary-tabs-title" class="visually-hidden">{{ 'Primary tabs'|t }}</h2>
<nav role="navigation" class="tabs-wrapper" aria-labelledby="primary-tabs-title" data-drupal-nav-primary-tabs>
<ul class="tabs tabs--primary">{{ primary }}</ul>
</nav>
{% endif %}
{% if secondary %}
<h2 id="secondary-tabs-title" class="visually-hidden">{{ 'Secondary tabs'|t }}</h2>
<nav role="navigation" class="tabs-wrapper" aria-labelledby="secondary-tabs-title">
<ul class="tabs tabs--secondary">{{ secondary }}</ul>
</nav>
{% endif %}
To get here, we copy from the current template file at olivero/templates/navigation/menu-local-tasks.html.twig
, but remove the {{ attach_library('olivero/tabs') }}
, as this is no longer needed with SDC.
Step 4: Point menu-local-tasks.html.twig to use the new component
Drupal will need to know when to load this template. To do this, we re-populate olivero/templates/navigation/menu-local-tasks.html.twig
with the following standard Twig include, and pass the two variables. Note that you can use standard Twig embeds syntax here if you need to pass blocks (aka slots).
{{ include('olivero:tabs', {
primary: primary,
secondary: secondary
}) }}
Step 5: Populate the tabs.component.yml
The final tabs.component.yml
will look like this:
$schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json
machineName: tabs
name: Tabs
status: BETA
schemas:
props:
type: object
required:
- primary
properties:
primary:
type: array
title: primary
secondary:
type: array
title: secondary
libraryDependencies:
- core/drupal
- core/once
To get here, we copy the example YML from the documentation, and modify it (once again, there will eventually be a Drush command to generate this). We look at the Twig template and see that there’s only two variables, primary
and secondary
, so we add these to the properties.
My first attempt with the YML was this
$schema: https://git.drupalcode.org/project/sdc/-/raw/1.x/src/metadata.schema.json
machineName: tabs
name: Tabs
status: BETA
schemas:
props:
type: object
required:
- primary
properties:
primary:
type: object
title: primary
secondary:
type: object
title: secondary
This failed because the primary
and secondary
variables are arrays – not objects. Fortunately, SDC was developed using JSON-Schema, which validates the types, and generates useful errors! This is vital in creating an excellent developer experience.
Changing the types of primary
and secondary
type to array
and then clearing the cache solved this issue. Note that when making changes to the YML definition, you will have to clear caches.
We also need to make sure that we add the appropriate library dependencies for our component. The tabs JavaScript relies on both the once
, and drupal
libraries. To do this, we add the following to the bottom of our YML definition (and once again clear cache)
libraryDependencies:
- core/drupal
- core/once
Upon a page refresh, we see that our (currently unstyled) markup is loading! 🙌
When we inspect the component, we can see additional HTML comments added (and highlighted with a 🥘paella emoji)! Note that the emoji will randomly change per component. This is useful when you have components nested in one another!
Step 6: Move the CSS and JavaScript
The next step is to move Olivero’s tabs.css
and tabs.js
files into the olivero/components/tabs
directory. Note we also move the source PostCSS file, as that’s what generates the compiled CSS.
SDC will automatically look for a *.css
, and .js
with the name of the component. I clear caches one more time (so SDC can detect the new files) and hallelujah! I see the tabs styled as expected!
One more note here. The PostCSS file that I moved has an import that needs to be corrected to point to the correct path.
@import "../base/media-queries.pcss.css";
Once we update it, we’re all set!
@import "../../css/base/media-queries.pcss.css";
Step 7: Remove old libraries definition and add module dependency
We now need to do a little bit of cleanup. We can remove the tabs
library definition from olivero/olivero.libraries.yml
. We also need to declare that our theme now depends on the SDC module. Luckily, as of Drupal 8.9.0, themes can now declare dependencies on modules!
To do this, we add the following to the bottom of olivero/olivero.info.yml
:
dependencies:
- drupal:sdc
We need your help!
Single directory components are being proposed to core as an experimental module. If we want to get this into Drupal 10.1.0 (due in June), we need to get it committed by May. To get this into core, we need your help!
- Read the comment questioning if SDC will be used, and if it’s the best option.
- Add a comment with your thoughts on how you will use it, and how this will affect your development workflow.
- If you have time, download the Drupal 10 patch and migrate your own components to try to find additional bugs.
Wrapping up
The front-end of Drupal has made some really big strides in the last few years, and this promises to be one of the biggest leaps of all. SDC promises to transform the way that we architect our Drupal themes, and promises to re-architect the way we build pages down the road.
I hope you’re as excited about SDC as I am!
Hey you! Leave a comment!8
Seriously... I really like it when people let me know their thoughts and that they've read this.
Great timing on this post, I had plans to look in to SDC today and this is the perfect introduction. I've followed along with this post to port the header-search component in Olivero to SDC and opened issue 3347736 with an MR and some observations.
Sounds interesting.
As a new developer learning how to create Drupal modules I am left wondering what the main benefit is of this new way of doing Module Development compared to how it is done now. As a new developer I am just beginning to understand how it is done now so I am having a bit of perplexity in grasping the benefits.
Maybe add a bit of context to this article to high light the current way vs the new way so new developers will clearly understand what this brings to the table.
Hi Michael,
Thanks for the reply. Note that this technique isn't really for module development -- it's for theming. Basically, right now the related files are scattered throughout the filesystem. This technique (that's not yet in Drupal core) allows you to easily group them into one folder.
Thanks!
Great work! :)
Two questions: Any intent to
Thanks!
Thank you for the great article!
It seems SDC overlaps with UI Patterns. However, UI Patterns seems to be more powerful at this point (supports configurations in View mode or Views display, preprocess ...).
You've mentioned a discussion with UI Patterns' maintainers, how would you see the future of SDC and UI Patterns? Are they gonna evolve in parallel, independently?
I was wondering the same thing. Some of the reasons why a new initiative started can be found in this Lullabot's article https://www.lullabot.com/articles/getting-single-directory-components-drupal-core.
However, indeed, it would be interesting to share the ui_patterns integration roadmap (vision ;).