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.

Display Modes and SDCs

Display modes and SDC components operate at different layers — understanding how they connect prevents confusion.

The rendering chain

1
View Mode → Paragraph Template → SDC Component

The SDC component has no knowledge of display modes. It only receives props and slots. The paragraph twig template is the bridge — it decides what data to pass. The view mode controls which paragraph template is used and how fields are formatted.

How content.field_name is affected

When you output content.field_image in a paragraph template, the rendered output respects whatever formatter and image style is configured in Manage display for that view mode. This is why you should use content.field_name for image and formatted text fields rather than the raw value.

Creating a view-mode-specific template

If a component needs to render differently in a listing context vs a full page, create a view-mode-specific paragraph template:

1
2
3
templates/paragraph/
├── paragraph--my-card.html.twig          # default view mode
└── paragraph--my-card--preview.html.twig # preview view mode

Both templates can embed the same SDC component but pass different props or use a different image style:

1
2
3
4
5
{# paragraph--my-card--preview.html.twig #}
{% include 'mytheme:my-card' with {
  variant: 'compact',
  label: paragraph.field_title.value,
} only %}

Configuring display settings

After creating a paragraph type, go to Manage display (/admin/structure/paragraphs_type/my_type/display) and:

  1. Set image fields to use a specific image style rather than the original
  2. Hide fields you don’t want available in content for that view mode
  3. Create additional view modes if the component needs to render differently in different contexts

Raw value vs rendered value — when to use each

SituationUse
Plain text, URL, list selectparagraph.field_name.value
Formatted text, body fieldscontent.field_name
Image fieldscontent.field_name (respects image style)
Media fieldscontent.field_name (respects media display)

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.