Post

Drupal Single Directory Components (SDC)

How to create and use Single Directory Components in Drupal using Drush and Twig.

Generate a Component with Drush

1
ddev drush generate sdc

Drush will prompt for the component name and machine name, then scaffold the files inside web/themes/custom/mytheme/components/your-component/.

Required Files

Each component lives in its own directory:

1
2
3
4
5
6
components/
└── my-card/
    ├── my-card.component.yml   # required
    ├── my-card.twig            # optional but useless without it
    ├── my-card.css             # optional — auto-loaded by SDC
    └── my-card.js              # optional — auto-loaded by SDC

SDC automatically loads the component’s CSS and JS — no need to declare them in libraries.yml.

Props and Slots

These concepts mirror JS frameworks like React and Vue.

Slots

A slot is the hole in your component where content goes — structured data, markup, or other components. Slots correspond to paragraph fields and are the content the user sees.

Props

Props are like HTML attributes — key/value pairs passed into the component. The user typically doesn’t see props directly; they control behaviour or styling (e.g. a CSS modifier class).

Component YAML

The .component.yml file is required and must include a props object, even if empty.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# components/my-card/my-card.component.yml
$schema: 'https://git.drupalcode.org/project/drupal/-/raw/HEAD/core/modules/sdc/src/metadata.schema.json'
name: My Card
description: A card component.
props:
  type: object
  properties:
    modifier:
      type: string
      title: CSS modifier class
slots:
  title:
    title: Title
  body:
    title: Body content

Component Twig

1
2
3
4
5
{# components/my-card/my-card.twig #}
<div class="my-card {{ modifier }}">
  <h2 class="my-card__title">{% block title %}{% endblock %}</h2>
  <div class="my-card__body">{% block body %}{% endblock %}</div>
</div>

Embedding in a Node Template

In node--my-card.html.twig, embed using themename:component-name:

1
2
3
4
5
6
{% embed 'mytheme:my-card' with {
  modifier: 'my-card--featured',
} %}
  {% block title %}{{ node.label }}{% endblock %}
  {% block body %}{{ content.body }}{% endblock %}
{% endembed %}

Passing Field Values via Paragraphs

In paragraphs--my-card.html.twig, pass the rendered field value (not the raw value) — this is Drupal best practice:

1
2
3
4
5
6
{% embed 'mytheme:my-card' with {
  modifier: modifier_class,
} %}
  {% block title %}{{ content.field_title }}{% endblock %}
  {% block body %}{{ content.field_body }}{% endblock %}
{% endembed %}

Using content.field_name (rendered) over paragraph.field_name.value (raw) respects formatters and field permissions.

Useful Modules

ModulePurpose
No MarkupStrips default field wrappers so you control all markup in Twig
Twig TweakHelper functions for Twig templates
SDC Style GuideVisual browser for all registered SDCs
SDC BlockExposes SDCs as placeable blocks
UI Patterns 2.xMaps fields to component props/slots
SDC DisplayUI for mapping slots without custom Twig
This post is licensed under CC BY 4.0 by the author.