Component Composition
When to Use
- You're including one component in another
- You need to decide between
include(),embed, or render arrays- You're nesting components
Decision
| If you need... | Use... | Why |
|---|---|---|
| Simple prop-only inclusion | include() with with_context = false |
Isolated, predictable, lowest overhead |
| To fill a slot via a Twig block | embed |
Only tag that can override a {% block %} — but only if the child renders that slot as a block |
| Programmatic composition (preprocess, controller, form, hook) | Render array (#type: component) |
The only path that sets both a block override and a context variable for every slot |
include() can also fill a slot, but only if the component template prints it as a variable ({{ header }}) rather than as a {% block %}. It cannot override blocks. That is the whole reason embed exists.
Precondition: the component you are embedding must render that slot through {% block name %}. If its template prints a bare {{ name }} instead, or wraps the block in {% if name %}, your block override is silently discarded — see Twig Templates in SDCs. Open the component's .twig before you write the embed.
Pattern
Pattern: include() Function (Most Common)
Use for simple component inclusion with props only.
{# Simple inclusion #}
{{ include('my_theme:button', {
text: 'Click Me',
variant: 'primary',
disabled: false
}) }}
{# With context control (recommended) #}
{{ include('my_theme:button', {
text: 'Save',
variant: 'primary'
}, with_context = false) }}
WHY with_context = false: Prevents automatic variable leakage into the component, keeping components isolated and predictable.
Pattern: embed Tag (Only for Slots)
Use when populating slots via Twig blocks.
Reference (component side): /core/themes/olivero/components/teaser/teaser.twig
{% embed 'my_theme:card' with {
title: node.label,
variant: 'featured'
} only %}
{% block content %}
{{ content.body }}
{{ include('my_theme:button', {
text: 'Read More',
url: node.url
}) }}
{% endblock %}
{% block footer %}
{{ content.field_tags }}
{% endblock %}
{% endembed %}
Pattern: Render Arrays (Programmatic)
Use in preprocessing, controllers, forms, hooks.
// In .theme file or controller
$build = [
'#type' => 'component',
'#component' => 'my_theme:card',
'#props' => [
'title' => $node->label(),
'variant' => 'featured',
],
'#slots' => [
'content' => $node->body->view('teaser'),
'footer' => $node->field_tags->view('compact'),
],
];
Pattern: Nested Components
Components can include other components via slots or direct inclusion.
{# Parent component with child components in slots #}
{% embed 'my_theme:hero-banner' with { variant: 'primary' } only %}
{% block content %}
<h1>{{ title }}</h1>
{{ include('my_theme:button', {
text: 'Get Started',
variant: 'primary',
size: 'large'
}) }}
{% endblock %}
{% endembed %}
Common Mistakes
Common Mistake: Using embed when include() is sufficient.
WHY: embed has overhead and complexity. Only use when you need to populate slots with Twig blocks. For props-only components, use include().
Common Mistake: Hardcoding child components instead of using slots. WHY: Reduces flexibility. Slots allow different child components in different contexts. Hardcoding couples parent to specific children.