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:
- Set image fields to use a specific image style rather than the original
- Hide fields you don’t want available in
contentfor that view mode - Create additional view modes if the component needs to render differently in different contexts
Raw value vs rendered value — when to use each
| Situation | Use |
|---|---|
| Plain text, URL, list select | paragraph.field_name.value |
| Formatted text, body fields | content.field_name |
| Image fields | content.field_name (respects image style) |
| Media fields | content.field_name (respects media display) |
Useful Modules
| Module | Purpose |
|---|---|
| No Markup | Strips default field wrappers so you control all markup in Twig |
| Twig Tweak | Helper functions for Twig templates |
| SDC Style Guide | Visual browser for all registered SDCs |
| SDC Block | Exposes SDCs as placeable blocks |
| UI Patterns 2.x | Maps fields to component props/slots |
| SDC Display | UI for mapping slots without custom Twig |