Problem/Motivation
When helping people with SDC, doing my consultancy job, on Drupal slack, or in contrib issues, I see a lot of confusion and struggle with the components slots, especially related to the embed and block mechanisms.
The current SDC documentation needs to be updated to:
- tone down the promotion of Twig
embedand Twigblock - preparing the people of using less the templates to templates calls and more the Render API (manually or through display buidling)
Twig blocks are problematic:
- Originally, they come from Jinja templating language, as a template inheritance mechanism. Something we never want to do in/with a component template.
- Then, Twig introduced
embedtag to use blocks for nesting. It is very Twig specific, and this is the reason why blocks may initially look interesting for SDC authors.
Slots declared as blocks are verbose and confusing.
For example, instead of {{ branding }} and {{ copyright }}:
- Radix do that in navbar:
{% block branding %}{{ branding }}{% endblock %} - umami do that in disclaimer :
{% block copyright %}{% endblock %}
(The radix way is better because compatible with both include and embed)
Some component authors are so confused that they overlap slots and props. For example, bootstrap's card:
{% block card_header_block %}
{{ card_header }}
{% endblock %}
Slots declared as blocks are complicated to call from another template.
For example, node--event.html.twig template which is passing some formatted fields to umami disclaimer's slots :
{% embed 'umami:disclaimer' only %}
{% block disclaimer %}
{{ content.field_date }}
{% endblock %}
{% block copyright %}
{{ content.field_location }}
{% endblock %}
{% endembed %}
Instead of the simpler and safer:
{{ include(
'umami:disclaimer', {
disclaimer: content.field_date,
copyright: content.field_location
},
with_context = false,
) }}Testing if a slot is empty seems complicated with a block.
Today, on #components Slack channel, brayn7 came with this snippet which was not working:
|
called by:
|
Removing the blocks and embed was a straightforward fix:
|
called by:
|
It is not possible to loop on a slot values when implemented as a Twig block
When we can expect multiple renderable injected in a slot (examples: grid, carousel, accordion...), we may want to loop on them:
{% set my_slot = my_slot and my_slot is not sequence ? [my_slot] : my_slot %}
{% for item in my_slot %}
<div class="a-markup-wrapper">
{{ item }}
</div>
{% endfor %}
It is not possible with blocks. It may be something we realize later in the lifecycle of a component and break the compatibility.
The 2 uses of blocks (embed & extends) are not always playing well together
See: #3446933: SDC incorrectly throws an exception about embedded slots
Twig block is a "template to template" only mechanism.
It is triggered only when a component is called from a presenter template (node.html.twig, field.html.twig...), not when its is called from the Render API. This create a gap between usage of SDC components, which will grow with the development of new display builders tools (SDC Display, UI Patterns 2, Experience Builder...).
Indeed, SDC API has currently a workaround to inject missing blocks with the Render API but this may not stay forever:
$template .= " {% block $slot_name %}" . PHP_EOL
. " {{ $slot_name }}" . PHP_EOL
. " {% endblock %}" . PHP_EOL;
}
Proper variables manipulation ({% for item in slot %}, {% if slot %}, {{ slot }}...) regardless of where the component is called, without the need of such a hack.
Proposed resolution
Update Quickstart
Add an example without block and with include() as the primary example.
Keep the example with block and embed, but add the print node:
{% block chip_content %}
{{ chip_content }}
{% endblock %}
Update Using your new single-directory component
Keep the mentions of block and embed but move it to the page bottom and update any parts which can make readers believing that "slots == Twig blocks". For examples:
Use the Twig include() function if there are no slots or arbitrary HTML properties and the data all follows a defined structure.
Use a Twig {% embed %} tag if you need to populate slots.
Also, don't promote the use of include tag at the same level of include function:
It is recommended to use the include function instead as it provides the same features with a bit more flexibility:
- The include function is semantically more "correct" (including a template outputs its rendered contents in the current scope; a tag should not display anything)
- The include function is more "composable"
- The include function does not impose any specific order for arguments thanks to named arguments.
Source: https://twig.symfony.com/doc/3.x/tags/include.html
Render element is already explained in this page, but it can be promoted a bit. It is a good opportunity to explain the difference between presenter templates:
- unavoidable because of the lack of display building:
breadcrumbs.html.twig,status-message.html.twig,menu.html.twig,pager.html.twig.. - the ones conflicting with the display building:
node.html.twig,field.html.twig,views.html.twig,block.html.twig...
Also, "Component data validation" section can be moved in "Creating a single-directory component" page.
Update What are Props and Slots in Drupal SDC Theming?
Remove any mention of block and embed. The goal of this page is to teach about slots and props, not to list all ways of calling components from templates.
A bit of rewording and an annotated component screenshot like this can also help the reader:

Update API for Single-Directory Components
Replace the example with block and embed by one with include. Or simply remove the example.
Also, a schema can be a good way to show how the different parts of the API are interacting.
Conclusion
SDC in Core is now 16 months old, and the community is still struggling with component templating and usage. I believe updating the confusing parts of the documentation will help the adoption and the blooming of the ecosystem.
| Comment | File | Size | Author |
|---|---|---|---|
| Capture d’écran du 2024-10-30 19-15-35.png | 339.07 KB | pdureau |
Issue fork drupal-3484727
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
quietone commentedComment #3
pdureau commentedAlso, le'ts remove this misleading section: Does SDC handle contextual links, attributes, title suffix, etc? because:
Comment #4
pdureau commentedOther idea for documentation update: help people to differentiate props and slots in presenter templates:
In
node.html.twig:nodevariable has the data to send to propscontenthas the data to send to slotsIn
user.html.twig:uservariable has the data to send to propscontenthas the data to send to slots...
Comment #5
dalemoore commentedI'm very much in favor of these updates! Your help on Drupal Slack a while back related to using
node/user/paragraphrather thancontentfor props was super helpful but that isn't anywhere in the docs IIRC.Would you also say that doing this:
in the component.yml is incorrect? Because I see that being done in Umami components, and in Experience Builder components. In my own components, I am noticing some bleeding in from the parent template when then doing adding the attributes prop and doing something like:
<msu-header{{ attributes }}>....</msu-header>in my msu-header.twig. Where if I include that component in my region--header.html.twig, it inherits the attributes from the parent div. If I try to use the 'only' keyword, like this:
then I cannot include slots/blocks that aren't explicitly included in the Twig template as blocks. Everything works fine without the only keyword, but the region div's class and other attributes get slapped onto my msu-header component, too.
If the attributes prop isn't right, then I would ask: how do we add a class, ID, or other attribute to our components from presenter templates? Having to manually add a
classprop (or other attribute) onto every SDC seem to be the only way, but and is burdensome.Doing something like this:
Doesn't work unless you explicitly create a
classprop on each component.If including an
attributesprop, I wonder if it would be better to name itcomponent_attributesinstead of justattributesto prevent the leaking of attributes from the presenter template. We already havetitle_attributes,content_attributes, etc. for other things.Comment #6
pdureau commentedHi @dalemoore,
It is not incorrect in a SDC POV but:
attributesvariable is automatically injected to the component anyway, so no need to add it to the component definitionDrupal\Core\Template\Attribute) as types is not compliant with JSON Schema and SDC has implemented a quirk to mange them. So let's avoid thisI ddin't know this about
embed, that's one more reason to promote Twiginclude()function (function, not tag !) (withwith_context = false!) instead of the messyembedtag.I would not recommend adding a
classprop.attributesprop is already perfect for this and it is not what is causing issue here.attributesalso allows to attach other attributes, and is expected by any mechanism adding semantic annotation, SEO tags, styles utilities from design systems, accessibility attributes...Radix theme did this mistake: #3482516: Inconsistent `attributes` handling Now the have have both
COMPONENT_attributesandattributesin some templates, which is confusing and may lead to other issues.Using the standard
attributesprop must be enough and work well.Comment #7
dalemoore commentedExcellent, thank you @pdureau! I know you have extensive experience working on components, SDC, and design systems. Thanks for sharing your insights on here, hopefully we can get these into the docs so others like me won't be confused and we can move towards some best practices.
I'll remove the attributes prop from my components. Also, do you have examples of how you're using SDCs? I downloaded UI Patterns, but there are no SDCs in there as examples. Or if you can direct me toward how you mean with using include rather than embed? I don't get how I can do slots with include and again, the docs only say use embed with slots.
Comment #8
pdureau commentedWe don't ship SDC component in UI Patterns 2 by design. UI Patterns 2 is only a "bridge" between SDC and other Drupal API.
However, we are currently adding an extensive documentation to UI Patterns 2, expected before Christmas, and I hope it will give you the information you need.
UI Suite team (the team doing UI Patterns, which I am part of) are also maintaining SDC themes:
Beware, there are 2 additions from UI Patterns 2 in those components compared to regular SDC components:
$def/$refmechanism to avoid repeating complex prop schemasBeside that, you can get inspiration from them.
Also, the tool we are using to check our SDC good practices, https://www.drupal.org/project/sdc_devel, is already usable, but the rules set needs some tweaking (wording, scope, levels...) which will happen around Christmas.
That's why the doc needs to be updated ;)
A summary (without the
onlyandwith_contextkeywords, for clarity):{% block label %}{% endblock %}{% embed my_component %}{% block label %}{{ label }}{% endblock %}{% endembed %}{{ label }}{{ include(my_component, {label: label}) }}{% block label %}{{ label }}{% endblock %}includeandembed, but heavy and error proneMy recommendation will be the second one: clear, simple, reliable.
Comment #9
pdureau commentedComment #10
dalemoore commentedThanks @pdureau! Very helpful info. Hopefully we can get some more eyes on this issue to get some more feedback from others who have been/are interested in starting to use SDCs.
Comment #11
dalemoore commentedWhile continuing my quest to slowly implement SDCs, I think it would also be beneficial in the docs to include actual real-world examples of using SDCs as @pdureau has done above. On Using your new single-directory component, all but one (the embed example) use hard-coded values in the props and slots. I think most of us are going to be wanting examples of how to get data from fields into those, and folks that are more frontend (🙋🏻♂️) are going to be struggling to figure out how to do that. Nowhere on the page does it mention how to do that (the only ways I know of are to install Devel/Kint, and just recently learned of Xdebug and have been trying to get it working in VScode).
So far, the only ways I know of to get data out of Drupal and into these SDCs are:
content['#block_content'].field_name.0.valuein a block.html.twig to get a prop value w/o the render bits)Are there other ways I'm not aware of? If so, they should be documented. If someone can help me answer these questions, I will try my first time at editing docs on here over the Christmas break. :)
Comment #12
pdureau commentedMost of the time, as the return value of configurable plugins related to display building:
Do we add this information to the SDc documentation too?
Comment #13
pdureau commentedComment #14
dalemoore commented@pdureau Yes I think that would be helpful to indicate for newcomers or non-backend folks how they meant to use for Using your component through a render array, if that isn't meant to be done in a theme. AFAIK everything that was supposed to go with an SDC is supposed to go in the theme in the same folder as the component, or at least done in the theme. But I don't think it's best practice to do any of that in a theme?
Comment #15
pdureau commentedStep by step, we are gathering information to update this doc. That's nice
Comment #16
pdureau commentedThe component are stored in a theme (or a module), but can be used outside of this theme (or module). Most of the time, component usage is stored in config entities related to display (block.block, entity view displays, views...). Sometimes in content (fields using ckeditor5 data or layout builder data...). Sometimes in code (custom plugins...)
Comment #17
idiaz.roncero+1 to this. I also fell on the misleading slots===blocks (or "blocks is the recommended and only implementation for slots") comparison, and other colleagues as well.
It will be much useful to emphasize that you can implement slots by using includes and embeds, and the tradeoffs and benefits of each approach.
Comment #18
pdureau commentedWe may do a documentation session with @mherchel during DrupalCon Atlanta
I am very excited about that. There are many sides of SDC documentation which need to be clarified or completed after 2 years of practice.
Comment #19
pdureau commentedData primitives mapping between the 3 formats we are manipulating:
Maybe adding such a table will be useful in the SDC documentation.
Comment #20
pdureau commentedSuggestion from @mlncn about
replacesproperty:Comment #21
nod_@pdureau, please go ahead with updating the documentation per specified in the issue summary, we can always refine later
Comment #22
pdureau commentedComment #23
nod_updating credits
Comment #24
pdureau commentedFrom Enrique Lacoma:
Also, tell something about custom & contrib Twig extensions.
Comment #25
pdureau commentedSo we are good. I am currently editing the documentation.
Comment #26
pdureau commentedUpdate Quickstart
✅ DONE.
✅ DONE. Switched to
include()functionUpdate Using your new single-directory component
✅ DONE.
✅ DONE.
✅ DONE.
NOT DONE YET
✅ DONE.
TODO
Update Creating a single-directory component
TODO
TODO
✅ DONE. 2025-05-09
TOOD
Update What are Props and Slots in Drupal SDC Theming?
✅ DONE. 2025-05-03
✅ DONE. 2025-05-03
Update API for Single-Directory Components
✅ DONE.
TODO
Update Frequently Asked Questions
TODO
✅ DONE.
Comment #29
pdureau commentedComment #30
ultimikeWow - this is all super-helpful. Huge kudos to @pdureau for taking this on.
As someone new to SDC, this discussion has been especially helpful in filling in some knowledge gaps for me.
-mike
Comment #31
wim leersPer @pdureau at #3493070-8: SDC `enum` props should have translatable labels: use `meta:enum`, this will also document
meta:enumto provide human-readable labels that also are translatable.For example:
— https://github.com/adobe/jsonschema2md/blob/f3b5773eb610130891503c1cf71b...
Comment #32
alex.skrypnykOne important aspect missing from the current documentation is a clear explanation of the underlying architectural approaches. While the documentation provides detailed information about the mechanics and implementation, it does not explain the rationale behind the recommended techniques.
Broadly, there are at least two common approaches:
1. Flat component structure with Render API – as promoted by UI Patterns, where HTML is composed by injecting subcomponents and applying styles externally. This approach often uses slots and discourages direct
include.2. Atomic design systems – where components are self-contained and reuse smaller parts through
include.Both approaches are valid and compatible with SDC. However, the documentation currently frames the first approach as a best practice, which may not suit all projects or design systems.
It would be beneficial to include a section outlining these architectural approaches, enabling developers to make informed decisions based on their specific needs and contexts.
Comment #33
pdureau commentedBoth approaches look atomic to me :) But let's not spend too much time on terminology.
I am all to describe the 2 approaches in documentation if both are relevant. Can you share with us:
Comment #34
wim leersComment #35
pdureau commentedIn "Slots & props data in a Drupal template", add a full example using both content renderable and typed data from object, into complex SDC props like icons.
https://www.drupal.org/docs/develop/theming-drupal/using-single-director...
Comment #37
sergey_gabrielyanHello @pdureau, reading this discussion was very helpful for me.
However, I have one case where I’m not sure what the best practice is.
In my project, I have a Card component that is used inside a Card paragraph. The card includes an image (rendered using the render array
content.field_image) and below the image there is content consisting of a title, description, and CTA.I’m unsure whether it’s better to:
Create a separate slot for each field and render them individually using
content.field_title, content.field_description, content.field_cta, and then usecontent|without(['field_image', 'field_title', 'field_description', 'field_cta'])to preserve proper cache metadata,or
Render the image separately and then simply use
content|without(['field_image'])for the rest of the fields, since the spacing between title, description, and CTA is the same and their styling is handled in the field templates.
I can build the card without mentioning each field explicitly. However, if that approach is not considered best practice, I’m ready to create a slot for each field.
I would really appreciate your advice on this.
Comment #38
pdureau commentedHello Sergey,
The best advice I could share, which works 90% of the time is to model your SDC without thinking about is use in Drupal. Model it to have the best implementation of the target UI component, keep it clean and focus on stateless UI logic, and Drupal integration will follow.
For your specific example:
Create a separate slot for each field and render them individually using content.field_title, content.field_description, content.field_cta, and then use content|without(['field_image', 'field_title', 'field_description', 'field_cta']) to preserve proper cache metadata,Field values can go to slots or props:
contentvariable (for example,content.field_cta) go to slotsnodevariable (for node templates) - (for example,node.field_cta.0.text) go to propsSo we are talking about slots here. You don't need to have a slot per field. As said before, the slot must match the UI logic we are implementing.
For example, your card component can have
header,bodyandfooterslots and then you can map:content.field_titleto headercontent.field_imageandcontent.field_descriptionto bodycontent.field_ctato footerDoes it make sense?
Comment #39
sergey_gabrielyanYes, thank you very much for the answer, @pdureau!
I have another question regarding
title_prefixandtitle_suffix. I understand that SDC does not handle these automatically like it does with attributes. However, I’m unsure whether it is necessary or recommended to usetitle_prefixandtitle_suffixinside a component.If I define them as
propsorslots, then the component starts following Drupal’s internal logic, which — as I understand from your explanation — is not recommended in the SDC approach.Alternatively, I could handle them in the template using
embed, or I could remove them entirely and not use them at all.I would really appreciate your advice on what would be the best approach in this case.