Component connector. Back to the Drupal origins.

Oleksii Haidabura
9 min readOct 11, 2023

--

History

Designing a theme for Drupal is an exhilarating process. Since the release of version 7, we have witnessed a multitude of transformative changes and a substantial increase in flexibility, primarily driven by the introduction of contrib modules and core itself.

Over the past decade, Drupal has undergone remarkable evolution, particularly in areas such as entity field management, layouts, and various other enhancements. This progress has necessitated significant updates in theme development to align with modern frontend techniques. Consequently, frontend developers have been challenged to find effective ways to work with Drupal’s offerings, with a particular focus on templates and included markup.

The rendering process in Drupal, however, has remained relatively unchanged, making it the cornerstone of what users see on web pages. In other words, it is the culmination of the “hook_theme” concept. Since version 8 the render pipeline did not get too many changes. It still uses well known old theme hooks, like : ‘html’, ‘page’, ‘region’, ‘menu’, ‘pager’ and so on. Developers may change the render process using hooks, template suggestions, preprocess hooks, etc. Sounds flexible enough. But in real life every customer comes with their own vision, and unique design. Working with the project, frontenders must rewrite a lot of html markup implementing pixel-perfect development.

Overriding themable output process is like ‘Groundhog Day’, it seems to repeat itself, but with different hooks!

A Common problem, which happens often, is when frontenders develop pixel-perfect development. Markup provided by frontenders becomes really different from what Drupal core (templates) provides, so team have several ways to implement changes:

  • Template suggestion — as result your mytheme/templates folders got tons of new files
  • Preprocess functions — responsibility is on developer, because this type of customisation may live in module or theme, and has custom hidden logic inside.

Usually customisation requires just additional classes, in other cases — libraries to be attached with custom js behaviors (select, checkboxes, etc). So, the theme grows and support becomes expensive, and if you are using search (solr), facets, commerce, and others.

Over the Last decade the Drupal community has been searching for a better mechanism of integration frontend, so we have two initiatives on top, which are: UI Suite and Single Directory Components(SDC). SDC doesn’t offer a complete solution for the Frontend Development team, it provides a component-driven theming approach with a basic structure. This structure can be further customized using various modules to fit specific workflows. The core concept is to keep all component-related elements, including Component Twig files, JS, CSS, assets, and a schema YAML file for component properties, within a single directory. This is a great idea to keep the frontend standalone, but SDC is still an additional feature.

SDC may not be the ‘Iron Man’ suit for Frontend Development, but it’s definitely the Tony Stark of theming, offering a component-driven approach

Even so, frontend developers can be blocked because of backend dependency, where frontend work can’t start without the backend work. On the other hand, the frontend community offers their own tools for showcasing their work, with Storybook being a well-known example. Additionally, it’s important to follow Atomic Design Principles & Methodology, although this can sometimes be challenging.

For instance, frontend developers may face obstacles when trying to use templates from atomic or molecular components directly, as it requires additional coding or mechanisms to connect specific Twig templates and integrate them into Drupal. Also the opposite case, when you actively use SDC and/or UI patterns, you need to integrate your components inside Storybook.

As a result, it’s always a significant dilemma to decide whether the egg should come first or the chicken.

There is no answer for that, but what we propose is — let’s turn back and remember the basics of Drupal render process, the main game player here is “hook_theme”. Everything is built over this render mechanism, so lets hack it.

Component connector

Introduction

Here we are presenting a Component Connector module which does some core “hacks”, and makes developers happy. Main idea of the module is that you may override, extend existing “hook_theme” implementations and create your own, using only 1 or 2 lines in the yml file.

Idea which is in the background is pretty simple — we allow frontend developers create and organize components in the way they want, and add just a simple yml file allowing this component to be integrated into Drupal theme registry directly. So it means we skip any possible additional steps for frontends components to be rendered.

As I mentioned before, for every project developers must rewrite markup of common components on the page, they usually are: menu, pager, breadcrumbs, layout, 90% of form elements, and many others, and the thing is — they must be rewritten globally. All these items use some “hook_theme” implemented by Drupal core or/and contrib modules, which means all information is stored in Drupal theme registry, so lets update Drupal theme registry itself.

We allow frontenders to create and store component html/css/js in a single directory, but propose to add a yml file which is responsible for Drupal integration. We have a name convention for ymls, a single component called “my-component” may have additional yml “my-component.theme.yml” or “my-component.suggestion.yml”, which type to use depends what kind of “integration” you want to implement.

Next i will describe how it works, it will be simplified explanation, for details, please check full Readme.md here https://git.drupalcode.org/project/component_connector/-/blob/1.x/README.md

To define component properties using YAML files for the Component Connector module, the following structure requirements should be followed:

Component Definition YAML Structure:

component_name: # Unique identifier for the component
hook theme: 'hook_theme_name' # Drupal hook theme name for rendering the component
base hook: 'base_hook_name' # Base hook theme name for inheritance (optional)
fields: # Additional variables or render elements for the component (optional)
field_name:
label: 'Field Label'
# Other field properties as needed
settings: # Additional settings for the component (optional)
setting_name: 'Setting Value'
libraries: # CSS and JS libraries required for the component (optional)
- library-name: # Unique identifier for the library, use "component_name"
css: # List of CSS files for the library (optional)
- 'file.css'
js: # List of JS files for the library (optional)
- 'file.js'

Suggestion Definition YAML Structure:

suggestion_name: # Unique identifier for the suggestion
base hook: 'base_hook_name' # Base hook theme name for inheritance

What we have here:

  • The component_name and suggestion_name should be unique identifiers for each component and suggestion, respectively.
  • The base hook property in the component definition indicates inheritance from an existing hook theme.
  • The hook theme in the component definition specifies the Drupal hook theme responsible for rendering the component.

Types of integration

Component Connector provides several ways of integrating components to become a real part of the Drupal theme registry.

  • Custom theme hook
  • Theme hook “candidate”
  • Replace existing theme hook
  • Theme hook suggestion
  • Theme hook layout

Custom theme hook.

This type of integration means you are creating a custom “hook_theme” for your component to be used in your code.

hook theme: ‘my_progress_percentage’

Example of component which should show some “progress” component with percentage:

my_theme/
|-- templates/
|-- |-- components/
| | |-- my-progress-percentage/
| | | |-- my-progress-percentage.html.twig
| | | |-- my-progress-percentage.css
| | | |-- my-progress-percentage.theme.yml

my-progress-percentage.theme.yml

my_progress_percentage:
label: My progress percentage
hook theme: 'my_progress_percentage'
fields:
value:
label: Value
libraries:
- my-progress-percentage:
css:
component:
my-progress-percentage.css: {}

my-progress-percentage.html.twig

<div class="my-progress-percentage">
<div class="my-progress-percentage__active" style="width: {{ value }}%;"></div>
</div>

my-progress-percentage.css

.my-progress-percentage {
background-color: #f0f0f0;
}

And then in your module, eg in field formatter for example you may use a new hook theme.

my_module/src/Plugin/Field/FieldFormatter/ProgressFormatter.php

  public function viewElements(FieldItemListInterface $items, $langcode): array {
$element = [];
foreach ($items as $delta => $item) {
$definition = $item->getDataDefinition();
$element[$delta] = [
'#theme' => 'my_progress_percentage',
'#value' => $item->value,
];
}
return $element;
}

Theme hook “candidate”

Drupal core provides a mechanism to extend existing hook_theme. For example we have core hook_theme “input” and you want to provide a custom component for the checkbox html element, In this case you need to have 2 lines in yaml.

  hook theme: 'input__checkbox'
base hook: 'input'

Example is similar to the previous one, difference is that in case of “candidate” we register candidate hook in Drupal theme registry, and it will be used automatically.

Replace existing theme hook

This type of integration replaces existing core/contrib hook with your own implementation. Yes, you may fully replace existing core(contrib) theme_hook, but be careful, with great power comes great responsibility.

Only one line is required.

  hook theme: 'fieldset'

Important notice: Drupal render mechanism allows us to use render element OR variables, inside hook theme, but not both.

See: https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21theme.api.php/function/hook_theme/10

and core implementation: https://git.drupalcode.org/project/drupal/-/blob/10.1.x/core/lib/Drupal/Core/Theme/ThemeManager.php#L191

So when you replace any hook theme, please be sure you are not using fields/settings properties in your yml. Also check initial hook_theme implementation/twig, to be sure you are using the same variables or render elements inside your html.twig.

Theme hook suggestion

This type allows you to use your template from your custom component as a template suggestion and extends the render array with additional variables. Name of the yml file in this case must be my-component.suggestion.yml. So, we replace reference to html.twig from Drupal theme registry for specified hook_theme and replace it with our my-component.html.twig file.

Bonus: Also this type of integration gives you availability to use additional variables, which may be defined in your yml. All fields/settings are passed to hook themes in the variables section, so we avoid limitations described in the previous section.

Only one line is required.

  base hook: 'pager'

Example of pager component:

my_theme/
|-- templates/
|-- |-- components/
| | |-- my-pager/
| | | |-- my-pager.html.twig
| | | |-- my-pager.css
| | | |-- my-pager.suggestion.yml

And my-pager.suggestion.yml

my_pager:
label: My pager
base hook: 'pager'
libraries:
- my-pager:
css:
component:
my-pager.css: {}

Theme hook layout

That’s kind of cherry on the cake, this type of integration is similar to Theme hook “candidate”, but “base hook” property should always has value “layout”

h_container:
label: Layout / Container
hook theme: 'h_container'
base hook: 'layout'
fields:
content:
type: render
label: Content
settings:
container_width:
type: select
label: Container width
default_value: default
options:
default: Auto width (Default)
w-1: Width 1
container_horizontal_padding:
type: select
label: Container horizontal padding
default_value: default
options:
default: No horizontal padding
hp-1: Horizontal padding type 1
hp-2: Horizontal padding type 2

In this case the new layout “Layout / Container” will be registered in Drupal theme registry and becomes available in the admin UI, where it’s allowed, in panels, entity view modes, etc.

Make sure you define the “fields” section, it will be transformed into “regions” property. Some helpful link: https://www.drupal.org/docs/drupal-apis/layout-api/how-to-register-layouts

If you will use the “settings” property, they will become available in the layout configuration section, which gives the opportunity for frontend developers to create flexible and reusable layouts.

Conclusion

So in this way frontend developers may organize components as they want, for example in case of using Storybook, folders structure may be like on example below:

my_theme/
| - fonts
| - css
| - templates
| | - components
| | | - suggestions
| | | | - m-form-element-label
| | | | - o-local-tasks
| | | | - m-fieldset
| | | | - m-local-task
| | | | - m-status-messages
| | | | - m-checkboxes
| | | | - m-details
| | | | - a-select
| | | | - m-form-element
| | | | - m-pager
| | | | - p-maintenance-page
| | | | - m-radios
| | | - layouts
| | | | - o-page-two-columns
| | | | - m-one-column
| | | | - h-container
| | | - theme
| | | | - a-input-email
| | | | - a-input-password
| | | | - a-input-checkbox
| | | | - m-menu-main
| - .storybook
| | - plugins
| | | - controls

Every component also has “my-component.stories.js” which is needed to build Storybook.

Notice: Component Connector module scan yml in your theme so folder name is on your responsibility

In yml definition you may use “settings” property, which may be helpful to pass additional settings in your theme_hook and use them later in your html.twig

Useful tools

Of course it’s not easy to understand what components you will use for your project, obviously common items like “pager”, “menu”, “details” or/and “fieldset”, input elements, like “input-password” also often need customization. Also usually you need 2–3 layouts, like one-two columns, card layout, etc.

For quick start we have prepared separate tool “Theme generator” for generating new theme from scratch: https://www.npmjs.com/package/@skilld/drupal-theme-generator

It provides a basic set of components, builds Storybook, and a lot of basic components already integrated in Drupal.

Second tool is “Component generator” to generate new components for existing theme: https://www.npmjs.com/package/@skilld/drupal-component-generator

It helps you to generate component:

  • Component type: atom, molecule, …
  • Integrate in your existing Storybook — yes/no, provide additional “my-component.stories.js”
  • Provide type of integration in Drupal: ‘layout’, ‘suggestion’, ‘theme’, ‘ui_patterns’

Summary

In summary, the Component Connector module streamlines Drupal theming for frontend and backend developers, making it more efficient. This module reduces the need for extensive template customization, offering greater flexibility and speed in Drupal site development. Overall, it’s a valuable addition to any Drupal developer’s toolkit, accelerating site development by 30%-50%.

In the world of Drupal theming, it’s like a ‘Groundhog Day’ for frontend developers dealing with hook_theme, templates, preprocessing. They rewrite components, tweak templates, and create endless variations. But with the Component Connector module, we’re breaking the cycle!

It’s the Bill Murray of Drupal theming — making your life easier one yml file at a time.

--

--