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
embed
and 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
embed
tag 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.