Integrating Paragraphs with Single Directory Components

Drupal’s Single Directory Components (SDCs) organize components in self-contained folders that automagically associate CSS, JS, and *.components.yml files without too much additional setup. This approach can be used with the Paragraphs module to create flexible and dynamic page layouts that delight site editors and front-end developers alike.

What are Single Directory Components?

SDCs are part of Drupal Core as of Drupal 10.3 and give front-end developers a more modern experience by encapsulating all necessary files within a single directory (Hence the name!). This typically includes a Twig template, a component YAML file, CSS, and JavaScript. The only files that are required for an SDC are the first two (Twig and YAML).

A note on Props & Slots

An important concept to understand first, is the difference between Props & Slots. Here is the documentation on the subject. But for our own rundown: Props refer to very basic pieces of data for a component. Think of an integer or string, but without any sort of HTML baggage. This might be the most useful, for example, if you need to pass a class name or a boolean value to the Twig template.

A Slot is unstructured data, or as it is described in the documentation, “nested content”. You can think of a Slot as content that is wrapped in a p tag or further nested in HTML. I would argue most of your content should fall under Slots, as it is best practice to not destructure Drupal’s render array. You might say you’ve often seen or have even yourself written templates where you drill into the render array, that might look like: content.field_description[0][‘#markup’]. So, why not just do that?

A brief tangent on Render Arrays

While it might be common practice to get variables by drilling down in such a way, the common way does not always equate to the best way! A handful of important elements get lost when you do so. I’ll only mention what is most pertinent to SDCs and Paragraphs here, though.

  1. Breaks Drupal’s caching system. Drupal’s page and render caches rely on cache tags/contexts that are metadata within render arrays. If you strip all that off, you can end up with stale HTML or even circumnavigate and disable caching altogether to get around the issue of said stale HTML. It’s better to just never mess with the caching system from the get-go.

  2. Bypassing preprocess functions and theme overrides. If you have any #theme, #theme_wrappers, or #pre_render callbacks on elements, they will never run.

  3. Drupal’s rendering engine optimizes and aggregates for performance. If you are circumventing the render array for the content, you are leaving that benefit on the table.

And now back to Props & Slots

So it’s important to understand the difference between when to use one over the other because this determination defines the structure of the *.component.yml file and affects the values that get passed from the Paragraph’s Twig template to the SDC’s Twig template.

Props & Slots in a *.component.yml file

name: Split Component
group: Components
props:
type: object
properties:
layout:
type: string
title: Layout
description: Default setting is 'Left Image & Right Content'.
enum: ['image_left', 'image_right']
default: 'image_left'
variant:
type: string
title: Hero Variant
description: Default setting is 'primary'. Translatable.
enum: ['primary', 'secondary', 'tertiary', 'quaternary', 'quinary']
default: 'primary'
slots:
content:
title: Content
description: Translatable and optional.
required: true
media:
title: Media
description: Required image or lottiefile, as it is used alongside the component content.
required: true

Props & Slots in a paragraph--*.html.twig file

  {% embed 'benzy:split-component' with {
layout: content.field_layout.0['#markup'],
style: content.field_style_variant.0['#markup'],
} %}
{% block media %}
{{ content.field_media }}
{% endblock %}
{% block content %}
{{ content.field_split_content }}
{% endblock %}
{% endembed %}

Paragraphs Module Overview

The Paragraphs module allows users to create structured content using smaller, reusable building blocks than the default Body WYSIWYG that comes when creating a node in Drupal. Paragraphs can be configured to contain various fields, enabling the creation of complex page layouts without extensive coding. Below is an example of a Paragraph in a node’s edit page:

Example of a Paragraph within a node edit page.

Integrating SDCs with Paragraphs

SDCs can be used to define the templates and logic for the look of individual Paragraphs. Basically, the site editors have a form for populating a component via Paragraphs and SDCs carry that over to the front-end experience.

Implementation Steps

Want to try your hand at connecting the two? Here is a brief step-by-step guide to help you:

  1. Create a Paragraph Type. Define a new Paragraph type in Drupal’s admin interface.
  2. Create an SDC Directory. Create a directory within a directory called components/ for the Paragraph component within your theme or module.
  3. Develop Component Files. Create the Twig template and *.component.yml file needed for the component. Include CSS and JS as needed, and make sure that all files share the same name. (E.g. split-component.twig and split-component.component.yml)
  4. Connect Paragraph Type to Template. Ensure the Paragraph type uses the Twig template from the SDC. You can do this with Twig’s embed tag. It is possible to do so with include as well, but according to the documentation, it is not recommended. See above for a code snippet that shows you how you’d pass props versus slots.

Conclusion

Using Single Directory Components with the Paragraphs module can significantly improve the organization, maintainability, and flexibility of Drupal websites. This approach allows for building complex pages with reusable building blocks, leading to efficient and scalable development. If you’d like to learn more and see how we built a Drupal-native component library to boot, check out our DrupalCon Atlanta 2025 talk!