Template Inheritance in Drupal 8 with Twig extend

Brian Gervais

While recently working on Stanford Parking and Transportation Services new Drupal 8 site, I had the opportunity use Twig’s extends syntax. Leveraging this simple, yet powerful feature makes Drupal 8 themes easier to maintain and reveals some helpful tools to make our templates more related and powerful.

Consider the tale of two pages. They are quite similar, the only difference being a class name on a single region. Page one has a class of ‘hotpink’ while page two has a class of ‘ghostwhite’. We’ll explore two methods for swapping out these class names in our twig template files.

Method 1: Define replaceable content with Twig ‘blocks’

Twig blocks are essentially swappable pieces of a template file. To override only the classname in the first template, we’ll extend it with the second template and swap the content between our replaceable block region.

First Template (page.html.twig)

<header>
  {{ page.header }}
</header>
 
{# REPLACE ME #}
{% block replaceable %}
  <section class="hotpink">
    {{ page.content }}
  </section>
{% endblock %}
 
</section>
 
<footer>
  {{ page.footer }}
</footer>

Second Template (page--two.html.twig)

{% extends  "page.html.twig" %}
 
{# Replace content_fields #}
{% block replaceable %}
  <section class="ghostwhite">
    {{ page.content }}
  </section>
{% endblock %}

There are two important components to this. First, we specify which template to extend. Second, we name a replaceable block in our first template and then replace it in our second template.

Of course, this is the simplest of examples. Twig’s block syntax lets us specify as many replaceable blocks as we want. It is only important to give each replaceable block a unique name.

Method 2: Replaceable Variables

Twig also lets us override variables from one template with those from another template. Thus, a cleaner way to achieve the same results as above would be to specify the class in the first template as a variable, then override it in the second template.

First Template (page.html.twig)

<header>
  {{ page.header }}
</header
 
{# REPLACE ME #}
{% block replaceable %}
  <section{{ attributes.addClass('hotpink') }}>
    {{ page.content }}
  </section>
{% endblock %}
 
</section>
 
<footer>
  {{ page.footer }}
</footer>

Second Template

{% extends  "page.html.twig" %}
 
{% set attributes = attributes.removeClass('hotpink').addClass('ghostwhite') %}

Less code = WIN!

The parent() function

I think it is worth mentioning one final thing: the parent() function. The parent function allows us to print content from a block in a parent template into the block of a child template.

Let’s say the only difference between our two pages was that the the second one includes a title above the content region. No need to duplicate all the HTML and Twig jargon from the first template; let the parent() handle that.

Second Template (page--two.html.twig)

{% extends  "page.html" %}
 
{# Replace content #}
{% block replaceable %}
  <h2>I am a Title</h2>
  {{ parent() }}
{% endblock %}

Twig represents a big improvement to Drupal. Using twig extends makes our templates lean and mean. Defining replaceable blocks lets us concentrate on just the content that is unique in child templates. The parent() function provides an easy way to print content from extended templates. Together, these features make our Drupal 8 themes incredibly flexible, maintainable, and far superior to the templates we had to use in Drupal 7 and earlier.