Icon Slots
When to Use
Your component needs maximum flexibility for icon content, including custom SVG, multiple icons, or complex icon compositions.
Decision
| Use slots when... | Use props when... |
|---|---|
| Icon content varies significantly | Icon is simple, single identifier |
| Multiple icons in one component | One icon per component |
| Custom SVG/HTML needed | Standard icon rendering sufficient |
| Icon with surrounding markup | Icon standalone |
Pattern
Slot-based icon in component:
# components/alert/alert.component.yml
name: Alert
props:
type: object
properties:
variant:
type: string
enum: [success, warning, error, info]
slots:
icon:
title: Alert Icon
description: Icon displayed before alert message
content:
title: Alert Content
Slot names are arbitrary keys. There is no implicit "default" slot in core SDC, and content is not special — a slot declared as default: must be rendered as {% block default %}, which is why the pair above is named icon and content to match the template.
Component template. Render every slot with {% block name %}{% endblock %}, not as a bare variable. Only the render-element path (#type: component with #slots) sets a slot-named context variable; under {% embed %} the value arrives as a Twig block, so {% if icon %}{{ icon }}{% endif %} is false and the caller's content vanishes with no error:
{# components/alert/alert.twig #}
<div class="alert alert--{{ variant|default('info') }}">
<div class="alert__icon">
{% block icon %}{% endblock %}
</div>
<div class="alert__content">
{% block content %}{% endblock %}
</div>
</div>
Usage with embed — this is the form the template above is written for:
{% embed 'my_theme:alert' with {variant: 'success'} %}
{% block icon %}
{{ icon('my_theme', 'check-circle', {
size: 24,
color: 'var(--bs-success)'
}) }}
{% endblock %}
{% block content %}
Operation completed successfully!
{% endblock %}
{% endembed %}
Usage with include. Here the slot values are context variables, so a template written with {% block %} will not pick them up — pick one calling convention per component and document it:
{{ include('my_theme:alert', {
variant: 'warning',
icon: icon('my_theme', 'alert-triangle', { size: 24 }),
content: 'Please review your input.'
}) }}
Reference: core/lib/Drupal/Core/Template/ComponentNodeVisitor.php for how slots are validated; core/themes/olivero/components/teaser/ for a core component whose .component.yml slot keys line up one-for-one with its {% block %} names.
Common Mistakes
- Rendering a slot as
{{ slot_name }}→ Works only viainclude(). Under{% embed %}it is undefined and the content silently disappears - Declaring a slot
default:and printing{{ content }}→ Two different names; nothing renders - Assuming
required: trueon a slot is enforced →ComponentNodeVisitor::validateSlots()reports undeclared slots, never missing ones - Using slots for simple icons → Props are simpler for standard icon rendering
- Missing slot documentation → Document expected slot content in the component description