Template Inheritance Basics
Extends
{% extends %} - the extends tag tells the template engine that this template “extends” another template.
{% extends 'base.html.twig' %}
{% block %} - the block tag is used to tell the template engine that a child template may override those portions of the template. A template that “extends” can’t output anything that isn’t in a block.
{% block content %}
Overridden content
{% endblock %}
For example:
Template base.html.twig
{{ title_prefix }} {% if title %} {{ title }} {% endif %} {{ title_suffix }} {% block content %} {{ content }} {% endblock %}
Template example-template.html.twig which extends base.html.twig
{% extends 'base.html.twig' %} {% block content %} {% if content %} {{ content }} {% endif %} {% endblock %}
Include
{% include %} - the include tag tells the template engine to return the rendered content of the included file into the current template.
{% include 'part.html.twig' %}
By default the {% include %} sets the scope of available data to all variables available globally in the parent template. For example we can set a variable {{ title }} at the top of the main template and by default, this title variable is considered in scope for any include template.
{% set title = node.title %}
In addition we can also explicitly pass data to the included template using the with keyword:
{% include 'part.html.twig' with {
foo1: bar1,
foo2: bar2,
}
%}
Also it's possible to disable access to the context by appending the "only" keyword, which allows us restrict the data that is passed into the included template:
{# no variables will be accessible #}
{% include 'part.html.twig' only %}
This example disables the active context of the parent template. None of the active context data will be available in the included template. This includes the title variable we set earlier.
{# only the title variable will be accessible #}
{% include 'part.html.twig' with {title: title} only %}
This example restricts data from the template active context but also includes only the data we explicitly set in using the keyword "with" (the title).
Finally, you can tell the template engine to ignore the statement if the template to be included does not exist by appending the "ignore missing" keyword right after the template name:
{% include 'part.html.twig' ignore missing %}
{% include 'part.html.twig' ignore missing with {'foo': 'bar'} %}
{% include 'part.html.twig' ignore missing only %}
For example:
Template header.html.twig
<header class="header"> {% if page.header %} {{ page.header }} {% endif %} {% if page.primary_menu %} <div id="header__nav--primary" class="header__nav--primary"> {{ page.primary_menu }} </div> {% endif %} {% if page.search %} {{ page.search }} {% endif %} </header>
Template page.html.twig
{% include 'header.html.twig' with {'page': page} %} {% if page.breadcrumb %} {{ page.breadcrumb }} {% endif %} <main class="main" id="main-content"> {{ page.content }} </main> {% if page.footer %} {{ page.footer }} {% endif %}
Embed
{% embed %} - the embed tag combines "include" and "extends" functionalities. It allows to include a template to another template and also to override any block defined inside the included template.
{% embed 'example-embed.html.twig' %}
{% block example %}
Overridden content
{% endblock %}
{% endembed %}
It also takes the same arguments as the {% include %} tag.
Macro
{% macro %} - the macro tag is useful to reuse template fragments to not repeat yourself.
Good example is a macro for menu links:
{% macro menu_links(items, attributes, menu_level) %}
{% import _self as menus %}
{% if items %}
...
{% for item in items %}
...
{{ link(item.title, item.url) }}
{% if item.below %}
{{ menus.menu_links(item.below, attributes, menu_level + 1) }}
{% endif %}
{% endfor %}
{% endif %}
{% endmacro %}
We can use a tag {% import %} to import macro into a local variable:
{% import _self as menus %}
And we can use it as
{{ menus.menu_links(item.below, attributes, menu_level + 1) }}
Macro arguments
Arguments of a macro can have a default value, eg
{% macro menu_links(items, attributes, menu_level = 0) %}
- Arguments of a macro are always optional.
- If extra positional arguments are passed to a macro, they end up in the special varargs variable as a list of values.
Layouts
We can use tags {% extends %}, {% include %}, {% embed %} to build powerful reusable layouts.
Let's see how in works on examples.
At first let's determine namespaces of the helper templates in the theme info file:
# EXAMPLE_THEME.info.yml
....
components:
namespaces:
base: components/base
....
Now we will be able to use the extra templates from the directory "components/base" via
{% TAG '@base/INNER_DIRECTORY/EXAMPLE_TEMPLATE.twig' %}
The directory "components/base" can have hierarchical structure and include as many inner directories as you want.
components/base/card/card.twig
It's common situation most of the listing templates includes items with the similar structure. Starting with building a basic template for the item. Will place it the directory "components/base/card".
# components/base/card/card.twig
{% set attributes = attributes|default(create_attribute()).addClass(['card']) %}
{% set card_title_attributes = card_title_attributes|default(create_attribute()) %}
{% set card_body_attributes = card_body_attributes|default(create_attribute()).addClass(['card-body-wrapper']) %}
{% set card_image_attributes = card_image_attributes|default(create_attribute()).addClass(['card-image-wrapper']) %}
<div {{ attributes }} data-card>
<div {{ card_body_attributes }}>
{% if card_title %}
{{ title_prefix }}
<h{{ card_title_level }} {{ card_title_attributes }}>
{{ card_title }}
</h{{ card_title_level }}>
{{ title_suffix }}
{% endif %}
{% block card_body %}
{% if card_body %}
<div class="card-body">
{{ card_body }}
</div>
{% endif %}
{% endblock %}
</div>
{% if has_image %}
<div {{ card_image_attributes }}>
{% if card_label %}
<div class="card-label">
{{ card_label }}
</div>
{% endif %}
{% block card_img %}
{{ card_img }}
{% endblock %}
</div>
{% endif %}
</div>
The card.twig template includes the following variables:
HTML attributes:
- attributes: HTML attributes for the main wrapper element
- card_title_attributes: HTML attributes for the title element
- card_body_attributes: HTML attributes for the body wrapper element
- card_image_attributes: HTML attributes for the image wrapper element
Variables for building the card title:
- card_title_level: Heading level of the card title
- card_title: Card title
Variables for building the card content:
- card_body: Content of the card body
- card_label: Card label
- has_image: Determine if the card image exists and should be displayed
- card_img: Card image
node--VIEW-MODE.html.twig/node--BUNDLE--VIEW-MODE.html.twig
Build the template of the node for required view mode which is used in the listing template based on the card template:
# node--VIEW-MODE.html.twig OR more specified node--BUNDLE--VIEW-MODE.html.twig
{#
/**
* @file
* Theme override to display a node.
*
* Available variables:
* ...
*
* @see template_preprocess_node()
*/
#}
{% embed '@base/card/card.twig' with {
card_body_attributes: create_attribute().addClass(['node-bundle-class'])
card_title: label,
card_title_level: 2,
card_body: content.field_teaser,
card_label: content.field_lable|default('EXAMPLE LABEL'|t),
has_image: not node.field_teaser_image.isEmpty(),
} %}
{% block card_img %}
{{ content.field_teaser_image }}
{% endblock %}
{% endembed %}
We use the "embed" tag here which allows us to include the card.twig template and to override the card_img block defined inside it.
components/base/heading/heading.twig
As you can see we use the following structure to build heading section in the card.twig template:
<h{{ card_title_level }} {{ card_title_attributes }}>
{{ card_title }}
</h{{ card_title_level }}>
We can improve this for more complicated heading section using one more extra template:
# components/base/heading/heading.twig
{% set attributes = attributes|default(create_attribute()) %}
<h{{ heading_level }} {{ attributes }}>
{% if heading_url %}
{% set heading_link_attributes = create_attribute().addClass(['card-heading-link']) %}
<a {{ heading_link_attributes.setAttribute('href', heading_url) }}>{{ heading }}</a>
{% else %}
{% if heading_prefix %}
{% block heading_with_prefix %}
{{ heading_prefix }} {{ heading }}
{% endblock %}
{% else %}
{% block heading %}
{{ heading }}
{% endblock %}
{% endif %}
{% endif %}
</h{{ heading_level }}>
The heading.twig template includes the following variables:
HTML attributes:
- attributes: HTML attributes for the main wrapper element
Variables for building the heading structure:
- heading_level: Heading level of the element
Variables for building the heading content:
- heading_url: The href attribute of the link to wrap the whole heading section to
- heading: Main content of the heading section
- heading_prefix: Content which can be displayed before the main content of the heading section
Include this heading.twig template into the card.twig template:
# components/base/card/card.twig
{% set attributes = attributes|default(create_attribute()).addClass(['card']) %}
{% set card_title_attributes = card_title_attributes|default(create_attribute()) %}
{% set card_body_attributes = card_body_attributes|default(create_attribute()).addClass(['card-body-wrapper']) %}
{% set card_image_attributes = card_image_attributes|default(create_attribute()).addClass(['card-image-wrapper']) %}
<div {{ attributes }} data-card>
<div {{ card_body_attributes }}>
{% if card_title %}
{{ title_prefix }}
<!-- Include the heading.twig template -->
{% include "@base/heading/heading.twig" with {
heading_level: 2,
heading: card_title,
} only %}
{{ title_suffix }}
{% endif %}
{% block card_body %}
{% if card_body %}
<div class="card-body">
{{ card_body }}
</div>
{% endif %}
{% endblock %}
</div>
{% if has_image %}
<div {{ card_image_attributes }}>
{% if card_label %}
<div class="card-label">
{{ card_label }}
</div>
{% endif %}
{% block card_img %}
{{ card_img }}
{% endblock %}
</div>
{% endif %}
</div>
So, as you can see, you can build multi-level inheritance model of the templates using these instruments. The multi-level model is a best-practice method so that the base template for a bundle can be easily overridden to properly extend your application's base layout.