How to nest a block in another block in an embed in Twig

While refactoring an application built in Twig, I came across a limitation of using embeds and blocks together.

Published August 10th 2017

The application makes heavy use of Twig's extend and embed functions to create easily replicable components and views. In this specific case, I was using the application's card component.

The view hierarchy was:

– Base
  – Identify
    – Login

I had embedded the card component within the identity template and planned to embed the email and password components in the login view.

Within the card component, there is a block called cardContent. This is used to provide an easy hook to customise the content within the card itself.

The idea was to call the block in the login template, which extends the identity template and embed the components there.

{% extends '.../base.html.twig' %}

{% block bodyContent %}
  ...
    {% embed '.../cards/card.html.twig' with {
      ...
    } %}
      {% block cardContent %}
        {% block content %}{% endblock %}
      {% endblock %}
    {% endembed %}
  ...
{% endblock %}

However, as it turns out, you can't override blocks that are nested within embeds. At least, not straight out of the box.

The solution

The solution to nesting blocks within blocks while in an embed turns out to be using set.

By creating a placeholder within the main block – and passing the nested block into that – you can take advantage of block inheritance within an embed.

Step by step

Add a set just below where you extend the parent template.

{% extends '.../base.html.twig' %}

{% set overrideContent %}

{% endset %}

Within that set, add the block you want to nest.

{% extends '.../base.html.twig' %}

{% set overrideContent %}
  {% block content %}{% endblock %}
{% endset %}

Then change your nested block into a placeholder:

{% block cardContent %}

{% endblock %}

Once done, you should end up with code that looks like this:

{% extends '.../base.html.twig' %}

{% set overrideContent %}
  {% block content %}{% endblock %}
{% endset %}

{% block bodyContent %}
  ...
    {% embed '@components/cards/card.html.twig' with {
      ...
    } %}
      {% block cardContent %}

      {% endblock %}
    {% endembed %}
...

{% endblock %}

And hey presto! You will be able to inject content into the nested block from the child view template.