Creating templates

Details

This page shows version 4.x. The current version can be found here.

We talked about the general architecture in the last part. Now we are focusing on what is or should be inside the templates themselves and how to use the various features Twig ships with.

  1. How to make extends, embeds and uses work?
  2. What’s so special with components and how to use them?
  3. How to use the various template features like insert tag rendering, images or translations?
  4. How should I name my templates?

Reusing template parts

Twig supports a lot of different ways how to reuse Template parts and Contao enhances nearly all of them as well with the managed namespace) concept: Extends, includes, embeds, horizontal reuse and macros.

Extends

A template can extend another one, called the parent or more general the base template. When extending a template, your template cannot contain content outside blocks, anymore, but you can adjust blocks of the parent template.

  • Typically, you want to replace and extend at the same time. This means, you name your template like the parent to make the loader choose yours when rendering, and then you reuse as much as possible from the parent by extending it.

  • This isn’t mandatory, though — variant templates are a good example for when you would want to create a new template but still only adjust things from the original one.

  • You can also use base templates like abstract base classes if you want to share a basic markup (implementation) across multiple children. The content_element/_base.html.twig is an example for this. Although this template does not contain a lot of markup, having it adds another handy use-case: If you want to adjust all content elements, e.g. by adding something to the wrapper, you now only need to adjust a single file.

Read more about the original implementation of the extends tag in the official Twig documentation.

Example

Extend another template

The original template @Contao/product.html.twig that displays a (fictive) product entity:

{# Outputs a product with title and some details. #}
<section class="product">
    {% block title %}<h1>{{ product.title }}</h1>{% endblock %}
    
    {% block details %}
        <ul class="product--details">
            {% for detail in product.details %}
                <li>{{ detail }}</li>
            {% endfor %}
        </ul>            
    {% endblock %}
</section>

We want to enhance the product title with an image:

{% extends "@Contao/product.html.twig" %}

{% block title %}
    {{ parent() }}
    <img scr="{{ product.imageSrc }}" alt="{{ product.title }}" />
{% endblock %}

With some demo data, you would then get the following HTML:

<section class="product">
    <h1>Fair-trade Coffee</h1>
    <img src="assets/images/coffee_package.jpg" title="Fair-trade Coffee"/>
    <ul class="product--details">
        <li>Nice aroma</li>
        <li>Lots of caffeine</li>
    </ul>            
</section>

With the {{ parent() }} function you can insert the content of the current block as it was defined in the parent template.

Best Practice

Unless you are replacing a block with something completely different, always try to include the parent’s content. This way multiple extensions can contribute things to the same block, and you automatically profit from their improvements and additions as well.

Includes

If you want to include a whole template in another one, you can either use the {% include %} tag or the include() function. While the tag looks analogous to when embedding templates, the function returns the template as a string, which might come handy in rare cases, where you need to further process the result before outputting. Which one you choose is a matter of taste.

Read more about the original implementation of the include tag or the include function in the official Twig documentation.

Example

Include a template partial

Assume you want to have a reusable call-to-action button. We create a simple @Contao/_action_button.html.twig template — we can enhance it later on without having to touch the places we are using it:

{% block button_container %}
    <div class="call-to-action">    
        <button>
            {% block button_inside %}
                {{ title|default('Click me!') }}
            {% endblock %}
        </button>
    </div>
{% endblock %}

Now we can include it anywhere:

<div class="advertisement">
    <quote>
        Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
        eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
        voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
    </quote>
    
    {% include "@Contao/_action_button.html.twig" with {title: 'Buy this poetry on a shirt!'} %}
</div>
Best Practice

Templates, that are only used by or inside others, but never directly rendered, are called partial templates or just partials. To make their usage directly obvious, prefix the filename with an underscore (_) like we did in the above example.

Embeds

Sometimes you want to include a template, but at the same time adjust some of its blocks. For this, you can embed the template.

Read more about the original implementation of the embed tag in the official Twig documentation.

Example

Embed a template partial

We continue the example from the includes section. But now, when inserting the button, we want to add an icon in front of the title. We overwrite the button_inside block of the embedded template just for this usage:

<div class="advertisement">
    <quote>
        Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
        eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
        voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
    </quote>
    
    {% embed "@Contao/_action_button.html.twig" with {title: 'Buy this poetry on a shirt!'} %}
        {% block button_inside %}
            <img src="files/poetry.svg" alt="Poetry"> {{ parent() }}
        {% endblock %}
    {% endembed %}
</div>

Horizontal reuse

Similar to what traits are for classes in PHP, in Twig the {% use %} tag allows to reuse blocks of other templates without extending them. Used blocks are not output by default, but instead made available to the block() function.

Warning

While includes and embeds are very powerful, there is a hidden pitfall when using them to build reusable templates. Whenever you use these functions, Twig creates a new isolated scope in the template, that won’t be accessible from outside. So, if a template A extends from a template B, that includes/embeds another template C, you won’t be able to access or adjust blocks of C inside A. Here, horizontal reuse comes to the rescue.

Horizontal reuse is one of the most powerful parts of Twig. At the same time it’s the most complex one. So let’s dissect the various parts needed for it to work.

  • The block() function renders a block, that is available to the current template. Available means: defined in the same file, any parent template or manually imported by a {% use %} statement (see below).

    {% block foo %}Foo {{ bar }}{% endblock %}
    
    {# Output the "foo" block again #}
    {{ block('foo') }}
  • When using block(), the function renders the given block with the current template parameters. To render it in a different context, you can surround it with the {% with %} … {% endwith %} block tag, which allows defining template parameters, that are scoped to just this block. By default, the defined parameters are merged with the current ones, to suppress merging, you can add the only keyword.

    {# Output the "foo" block with the "bar" parameter being set to "baz" #}
    {% with {bar: 'baz'} only %}
      {{ block('foo') }}
    {% endwith %}
  • Finally, the {% use %} statement imports all blocks of the given template without outputting them. They will
    then be available to be output using a combination of {% with %} and the block() function. When importing, it could potentially happen, that there are blocks with identical names. To work around this, you can rename the blocks while importing.

    {% use "@Contao/_foo.html.twig" %}
    {% use "@Contao/_cat.html.twig" with sound as meow  %}
    {% use "@Contao/_dog.html.twig" with sound as bark %}
    
    {# Output the "sound" block of the cat template, that was imported as "meow" #}
    {{ block('meow') }}

Read more about the original implementation of the use tag, the with tag and the block function the in the official Twig documentation.

Example

Make blocks available with horizontal reuse

We again build on top of the example of a button partial from the includes section. But this time, we make it into an abstract reusable @Contao/_advertisement.html.twig template:

{% use "@Contao/_action_button.html.twig" %}

<div class="advertisement">
    {% block description %}{% block %}
    {{ block('button_container') }}
</div>

Now, let’s actually use it in our @Contao/poetry_advertisement.html.twig. We are extending the just created template and adjust its own and its used blocks to get the same result as in the embed example:

{% extends "@Contao/_advertisement.html.twig" %}

{% block description %}
    <quote>
        Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
        eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
        voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
    </quote>
{% endblock %}

{% block button_inside %}
    <img src="files/poetry.svg" alt="Poetry"> {{ parent() }}
{% endblock %}

Did you note, how we were also able to adjust the button_inside block, here? We can either adjust this block in the original @Contao/_action_button.html.twig template (to affect all action buttons), in the @Contao/_advertisement.html.twig (to affect all buttons in advertisements) or, like we did here, in the @Contao/poetry_advertisement.html.twig (to only effect this special advertisement). Neat!

Best Practice

In Contao we are using the term component when we mean a piece of reusable template logic, that is intended to be imported with the {% use %} statement. In order for it to be output in the block() function, we make sure our whole component code is wrapped into a single block. As a convention, we call the block <name>_component. We also put each component in its own file inside the component directory.

Example: The figure component lives in the @Contao/component/_figure.html.twig file and is wrapped in a figure_component block. Read more about this in the section about Contao components.

Macros

Twig also features macros via the {% macro %} tag. These are very similar to how PHP functions work and also allow to reuse template logic. Head over to the Twig docs, to read more about how the macro tag works in detail.

Example

Using macros to render a recursive structure

Macros behave like functions, they are well suited if you want to encapsulate a certain logic. Here we want to render a recursive tree structure (like a menu). Each node can contain more child nodes, and only the leafs contain values:

{% macro tree(node) %}
    <ul>
        {% for child_node in node.children %}
            <li>
                {% if child_node.children|length %}
                    {{ _self.tree(child_node) }}
                {% else %}
                    {{ child_node.value }}
                {% endif %}
            </li>
        {% endfor %}
    </ul>
{% endmacro %}

<div class="tree">
    {{ _self.tree_node(tree) }}
</div> 
Info

Do not overuse macros, especially in extensions. They can help avoid duplication in your code, but they are way less adjustable for others. Many things can also be done by using blocks and the block() function instead.

Contao components

By understanding how components work in Contao, you can get the most out of the core templates. You also might want to reuse them in your own code. Please familiarize yourself with the concept of horizontal reuse as we are heavily going to make use of it.

Let’s look at a real world example first. In Contao, a template component could look like this (taken from @Contao/component/_figure.html.twig, a bit simplified):

{% use "@Contao/component/_picture.html.twig" %}

{% block figure_component %}
    {% set figure_attributes = attrs()
        .mergeWith(figure.options.attr|default)
        .mergeWith(figure_attributes|default)
    %}
    <figure{{ figure_attributes }}>
        {% if not figure.linkHref|default %}
            {# Media #}
            {% block media %}
                {{ block('picture_component') }}
            {% endblock %}
        {% else %}
            {# Media wrapped with link #}
            {% block media_link %}
                {% set link_attributes = attrs(figure.linkAttributes(true)|default)
                    .set('title', figure.metadata.title)
                    .mergeWith(figure.options.link_attr|default)
                    .mergeWith(link_attributes|default)
                %}
                <a{{ link_attributes }}>{{ block('media') }}</a>
            {% endblock %}
        {% endif %}

        {# Caption #}
        {% block caption %}
            {% if figure.metadata and figure.metadata.caption %}
                {% set caption_attributes = attrs()
                    .mergeWith(figure.options.caption_attr|default)
                    .mergeWith(caption_attributes|default)
                %}
                <figcaption{{ caption_attributes }}>
                    {%- block caption_inner -%}
                        {{- figure.metadata.caption|raw -}}
                    {%- endblock -%}
                </figcaption>
            {% endif %}
        {% endblock %}
    </figure>
{% endblock %}

Wow, there is a lot going on. For a moment, let’s remove the HTML attributes and strip the example further down.

It is no easier to see the <figure> HTML tag with a <figcaption> and some media, optionally wrapped in an a tag. Everything is placed in a block called figure_component.

{% block figure_component %}
    <figure>
        {# Media, optionally wrapped in a link #}
        {% if not figure.linkHref|default %}
            {% block media %}
                … here would be some media, like a picture …
            {% endblock %}
        {% else %}
            <a>{{ block('media') }}</a>
        {% endif %}

        {# Caption #}
        <figcaption>
            … here goes the caption …
        </figcaption>
    </figure>
{% endblock %}

Now, try going back to the original version. You’ll find, that the “media” bit isn’t implemented in the template, but instead uses and renders another component, the picture component (yes we’ve got template reuse everywhere). You also see the “set and merge” pattern being heavily in use (more on that further down). And we’ve got a lot of blocks. All this makes the template look more complex, but extremely adjustable on the other hand.

Note

Don’t worry — your templates do not have to feature the same level of adjustability. Consider it, when creating an extension that ships components, though. For the Contao core, we try to value adjustability a bit higher in general, as these templates are likely to be handled by a lot of parties.

Adjusting components

Talking about adjusting: You saw in the examples earlier in this article how you would adjust blocks introduced by components. But how would you adjust a component itself (globally)?

Info

You cannot extend components! In general: Twig does not allow to extend from templates that are being “used” at the same time. Use and overwrite the component’s blocks instead (see example below).

Example

Globally adjusting the picture component

Let’s assume you want to add a custom class to every <img> tag and also wrap it in a div container. Luckily, this can be done with minimal effort — you would overwrite the @Contao/component/_picture.html.twig template, import the original templates’ blocks, and then make the needed adjustments:

{% use "@Contao/component/_picture.html.twig" %}

{% block image %}
    {% set img_attributes = attrs().addClass('my-image').mergeWith(img_attributes|default) %}
    <div>{{ parent() }}</div>
{% endblock %}

Although, we are overwriting a block without extending a template, we can use the parent() function. It works as expected outputting the original block’s content. The rest of our adjustments is basic HtmlAttributes behavior — we set a variable, we know the parent block is going to merge and use, and we’re done.

Grouped parameters

Until now, we only looked at the figure and picture components. They are special in a way, that every data they output, comes from a single Figure object (note the figure template parameter being used everywhere). Most of the time, you want to use several parameters, though.

If you dare, take a look at the list component, another very powerful, yet complex component: source code. Don’t worry about the javascript magic to sort and randomize lists at the moment but instead focus at the very beginning. Here is a stripped down version:

{% block list_component %}
    {% set list = list|default(_context) %}
    …
    
    <{{ tag_name }}>
        …
    </{{ tag_name }}>
{% endblock %}

The component begins with a variable declaration for a variable called list (not by accident: like the component), that, if not already existing, will be set to the current context using the special _context variable. Inside the component, everytime a parameter is used, the list version is used: list.items, list.randomize_order, and so on.

This effectively allows passing parameters individually or grouped under a Twig object. But why? When outputting multiple components, you can neatly group the data (already in the controller). By using the default names (here rendering with parameters: ['headline' => […], 'list' => […]]), you can omit any mapping needed with a {% with %} block. More importantly, the component’s parameters will never collide if named the same.

{# Output a headline and a list #} 
{{ block('headline_component') }}
{{ block('list_component') }}

On the other hand, manually outputting blocks is less cumbersome without the additional group:

{% with {tag_name: 'h2', headline: 'My headline' } %}
    {{ block('headline_component') }}
{% endwith %}
Best Practice

When creating components, allow grouping their parameters under a single object named like the component. For this add a {% set <name> = <name>|default(_context) %} variable definition to the beginning of your component and then only use the defined variable further down. Bonus points are given, if you add a little doc block to your file, listing the available parameters and how to use them (look at the core’s components for some inspiration).

Template features

The Contao Twig extension does most of the setup needed to provide our custom functionality, like the managed namespace, the input encoding tolerant escapers, the legacy interoperability, or several filters and functions to make your life easier. On top of that, many Twig features just work out of the box for us.

Insert tags

Warning

Make sure you read and understood the section about encoding before continuing!

If you want to output a string, that contains insert tags, you, as the template designer, need to decide if and how it should get treated. In case insert tags should get replaced, you can precisely control what to encode and what to keep raw (see table below): Everything, all but the result of evaluating the insert tags, or nothing at all. This way you can keep the security risk low while still allowing features.

The examples in the following table use the context ['text' => 'This is some <b>very</b> {{br}} rich text.] that both includes some user input HTML and the {{br}} insert tag (which evaluates to <br> if being replaced). The given output examples were generated using the escaping strategy for HTML.

HandlingUsageExample output in a HTML context
plain text
treat insert tags as text,
encode everything
{{ text }}This is some &lt;b&gt;very&lt;/b&gt; {{br}} rich text.
replace
encode everything
{{ text|insert_tag }}This is some &lt;b&gt;very&lt;/b&gt; &lt;br&gt; rich text.
replace and trust insert tags
⚠️ only encode the text around the insert tags
{{ text|insert_tag_raw }}This is some &lt;b&gt;very&lt;/b&gt; <br> rich text.
raw text
treat insert tags as text,
⚠️ encode nothing
{{ text|raw }}This is some<b>very</b> {{br}} rich text.
replace and trust everything
⚠️ encode nothing
{{ text|insert_tag|raw }}This is some <b>very</b> <br> rich text.

Think about your scenario, before deciding which option to use. Should insert tags even be replaced? If so, they might for instance output HTML. Do you trust this? And, sticking to the HTML example, should a user be able to write HTML? With |insert_tag_raw you can for example allow an editor to use the {{br}} insert tag while still disallowing the use of any custom HTML around it.

Best Practice

There is also an insert_tag() function to directly render an insert tag, but you should avoid using it if possible. Use proper template functions and filters instead. Keep in mind, that insert tags are meant to be used by editors inside the back end and not to structure content in templates.

Images

You can output a figure directly from within your template by using the contao_figure Twig function. The function expects a resource (uuid, id, path) as the first and the image size as the second argument. If you want to specify more config, you can pass a config object as the third argument.

Note

This has been deprecated in Contao 5.0 in favor of the figure function.

In the config object you can configure the same things you would as when using the FigureBuilder (see [reference][FigureBuilderOptionsReference]). In fact, under the hood, the Twig function uses [PropertyAccess][PropertyAccess] to configure a FigureBuilder instance. This is why you can also use the same short notation you expect from Twig when accessing variables (e.g. size instead of setSize). In the case of metadata, you can also just pass an object which will internally be converted into Metadata.

{# It's enough to specifiy the resource and size… #}
{{ contao_figure('path/to/my/image.png', '_my_size') }}
    
{# …but you can also go wild with the options. #}
{{ contao_figure(id, [200, 200, 'proportional'], { 
    metadata: { alt: 'Contao Logo', caption: 'Look at this CMS!' },
    enableLightbox: true,
    lightboxGroupIdentifier: 'logos',
    lightboxSize: '_big_size',
    linkHref: 'https://contao.org',
    options: { attr: { class: 'logo-container' } }
}) }}
Note

By default, the figure.html.twig template (see 1) is used to render the result, but you can optionally pass a custom template as the fourth argument. The template will receive a figure variable with your configured Figure as its context.

If you want to use a complex size configuration for just one image, you do not need to create a global configuration. Instead, use the picture_config Twig function, pass it the configuration and use the result as the size argument of the contao_figure function:

{% set special_size = picture_config({
    width: 400,
    height: 400,
    resizeMode: 'proportional',
    sizes: '0.75,1,1.5,2',
    items: [{
        width: 200,
        height: 100,
        media: '(max-width: 140px)',
    }]
}) %}

{{ contao_figure(uuid, special_size) }}

Translations

The Symfony Twig bridge includes a trans tag and function to handle and output translations. If you want to define the default translation domain, that is used in the whole template, you can use the trans_default_domain tag. Read more about this in the Symfony Twig documentation.

{% trans_default_domain "contao_default" %}

<div>
    {# Output a translation using the default domain: #}
    <a href="more.html">{{ 'MSC.readMore'|trans }}</a>

    {# Output a translation containing placeholders and a custom domain. You
       can either use an array with unnamed parameters, or an object, that
       explicitly defines replacements: #}
    {{ 'tl_race.result'|trans([best_time], 'contao_race') }}
    {{ 'tl_race.result'|trans({'%best%': best_time}, 'contao_race') }}
</div>
Tip

If you need a translation in a certain locale, this is also possible by passing the locale as an argument to the function. For example trans([], 'contao_default', 'de') or trans(locale='it').

URLs

To generate URLs from within Twig templates, you can use the path() function, that is shipped with the Symfony Twig bridge. Read more about this function in the Symfony Twig documentation.

<a href="{{ path('my_custom_route', {id: 42}) }}">Watch for this!</a>
Warning

As of writing this, there is unfortunately no replacement for PageModel::getFrontendUrl(), yet. If you need this, for now, stick to generating the URLs in your controller and then pass them to your template (where they are only output).

Naming convention

Because of the shared @Contao namespace, it is important, that the Contao ecosystem tries to stick to a common naming convention. For the sake of readability, we are omitting the @Contao/ prefix to the logical name in the following examples.

Best Practice

Here are some general rules to consider for directory as well as template file names:

  • Use snake_case, such as content_element or foo_bar_baz.
  • Only use lowercase characters and avoid special characters. Don’t use dots except for the file extension.
  • Always include the file type and .twig as a file extension, e.g. cat.svg.twig or slider.html.twig.
  • The name already includes the directory/subdirectory, never repeat any directory name in the file name (content_element/text/text_highlight.html.twig → content_element/text/highlight.html.twig).

Directory structure

Twig allows the usage of directories as part of the template name. For extensions or if you want to use the application’s main template directory, you might want to read the part about how to properly set up the Twig root first.

Best Practice

We strongly suggest, that you organize your templates like outlined below. Especially extensions should stick to these conventions, so that users do not have to mix different structures in their application’s template directory!

Put your templates in one of the following directories in your Twig root. Only create further subdirectories inside these directories, if there is a justified need for it. You might want to pitch your idea on GitHub first, so that we can potentially make it available here for others to use as well.

CategoryDirectoryTemplate example (@Contao/…)
Reusable componentscomponentbackend/module_wildcard.html.twig
Content elementscontent_elementcontent_element/gallery.html.twig
Frontend modulesfrontend_modulefrontend_module/feed_reader.html.twig
Anything related to the back end
Until we further refactor the back end, this directory is reserved for the Contao core.
backend@Contao/backend/module_wildcard.html.twig
Forward compatibility
This directory is reserved for extensions, that need to provide templates in order to be forward compatible. You should never extend from these templates, nor render them directly.
compatcompat/content_element/code.html.twig
Extension-specific things<extension>*)notification_center/mail.html.twig

*) Only put things in an extension-specific directory, if it won’t fit in any other category. If your extension includes a lot of templates, it is fine to create a subdirectory <extension> (e.g. several content element templates for the foo extension: content_element/foo/…). Do not use a vendor prefix if your extension has a unique name (e.g. notification_center) or only makes sense to have one of a kind to be installed at the same time (e.g. opengraph for an extension that adds opengraph tags). The same holds true for the template file names themselves.

Variants

In Contao, you can create templates in multiple variants and let back end users pick the one they need. Read more about the architecture and how they are used in the template variants section.

Best Practice

Variant templates should be put in a directory named like the template’s file name excluding the file extension.

Example: A variant to the content_element/gallery.html.twig template, should be placed in the content_element/gallery directory. The file name should describe what the variant is about, e.g.: content_element/gallery/carousel.html.twig if images get displayed in a carousel instead.

since 5.2 If you want to change the display names of the templates, that are selectable in the back end dropdowns for content elements or frontend modules, you can create translations for them in the templates translation domain. The translation ID is equal to the template identifier:

# translations/templates.en.yaml
content_element/text: 'Default text'
content_element/text/special: 'Special text'

Partials and components

Partials are files that will never be rendered directly but included/embedded/used or extended from only. There is no difference in how they work, but the convention to distinguish them allows to easier find the main controller templates.

Best Practice

Include a leading underscore to a partial’s name. For instance: content_element/_base.html.twig. The same applies for component files, such as component/_list.html.twig. Follow the rules about directories to decide where to put your partial. It’s fine, and also quite typical, that they coexist in the same directory as the files that are using them.