Sewing supplies

Drupal Code Standards: Twig in Drupal 8

This is the seventh post in a series about coding standards. In this post we’ll talk about how to adhere to standards while implementing Twig templating in Drupal 8.

Other posts in this series:

  1. Drupal Code Standards: What Are They?
  2. Drupal Code Standards: How Do We Implement Them?
  3. Drupal Code Standards: Formatting
  4. Drupal Code Standards: Documentation
  5. Drupal Code Standards: The t() function
  6. Drupal Code Standards: Object Oriented Coding & Drupal 8
  7. Drupal Code Standards: Twig in Drupal 8

The DocBlock

Twig files should include a docblock like any other Drupal file. A docblock is a specially formatted block of information that goes at the top of every file, class, and function. See our post on documentation to brush up if you’ve forgotten the finer points of docblocks!

Sections such as @see, @ingroup, etc, still apply as they did before Twig, so use as appropriate. You can consult our guide here or the Drupal documentation.

A note on @ingroup themeable from Drupal.org:

Twig template docblocks should only include @ingroup themeable if the template is providing the default themeable output. For themes overriding default output the @ingroup themeable line should not be included.

Variables

Variables should be referenced only by name, with no prefix. For example, foo instead of $foo or {{ foo }}. The type should not be included, as this does not affect theming.

Here’s an example from the Token contrib module:

{#
/**
 * @file
 * Default theme implementation for the token tree link.
 *
 * Available variables:
 * - url: The URL to the token tree page.
 * - text: The text to be displayed in the link.
 * - attributes: Attributes for the anchor tag.
 * - link: The complete link.
 *
 * @see template_preprocess_token_tree_link()
 *
 * @ingroup themeable
 */
#}
{% if link -%}
  {{ link }}
{%- endif %}

In the above code, you can see that the docblock lists the available variables. They are listed without a prefix or type. This is the default template, so it uses @ingroup themeable. Also notice that there is no period at the end of any line starting with a @ tag.

Variables referenced inline in a docblock should be wrapped in single quotes.

Here's an example from Drupal 8 core, the Comment module:

/*
...
 * - created: Formatted date and time for when the comment was created.
 *   Preprocess functions can reformat it by calling format_date() with the
 *   desired parameters on the 'comment.created' variable.
 * - changed: Formatted date and time for when the comment was last changed.
 *   Preprocess functions can reformat it by calling format_date() with the
 *   desired parameters on the 'comment.changed' variable.
...
*/

The above code is from comment.html.twig, which has a very lengthy docblock, but you can see from this snippet that these variables are properly referenced and wrapped in single quotes.

Expressions

Twig makes it easy to check if a variable is available before using it. Just use if. Here's an example from Drupal 8 core:

  {% if label %}
    <h2{{ title_attributes }}>{{ label }}</h2>
  {% endif %}

In the above code, we can see that the code checks for the availability of label with if label before printing it. This is a lot simpler than previous methods of checking, which required more complex structures and more code.

Loops are also much simpler in Twig! You can easily use for loops in Twig. Here's an example from the Devel contrib module:

{% for item in collector.links %}
  <div class="sf-toolbar-info-piece">
    <span><a href="{{ item.url }}" title="{{ item.description|default(item.title) }}">{{ item.title }}</a></span>
  </div>
{% endfor %}

The above code will loop through and print out each item in collector.links.

If you need to use a key and a value in your for loop, you can accomplish that as well. Here's an example from the Ctools contrib module:

  {% if trail %}
    <div class="wizard-trail">
      {% for key, value in trail %}
        {% if key is same as(step) %}
          {{ value }}
        {% else %}
          {{ value }}
        {% endif %}
        {% if value is not same as(trail|last) %}
          {{ divider }}
        {% endif %}
      {% endfor %}
    </div>
  {% endif %}

In the above code, you can see that for key, value in trail gives us the expected key and value pair for a foreach loop, and they can be used throughout the body of the loop.

Another simple expression that can be done in Twig is variable assignment. If a variable is needed only in the template, it can be declared directly, as you would anywhere else. Like this:

{% myvariable = 'myvariable value' %}

For more on how to translate variables, see our post on the t() function, and the section on Drupal 8 & Twig.

HTML attributes

HTML attributes in Drupal 8 are drillable. They can be printed all at once, or one at a time, using dot notation. If you do not print them all, they should all be included at the end, so that any other attributes added by other modules will be included. If you're not familiar with HTML attributes in Drupal 8, here's some Drupal.org documentation, and the Drupal Twig documentation on HTML attributes. Here's an example from Drupal 8 core:

{% if link_path -%}
  <a{{ attributes }}>{{ name }}{{ extra }}</a>
{%- else -%}
  <span{{ attributes }}>{{ name }}{{ extra }}</span>
{%- endif -%}

In the above code, the full attributes are printed for the <a> and <span> tags.

You can also add or remove a class in Twig. Here's some documentation on Drupal.org, and here's an example from the Views module:

{%
  set classes = [
    dom_id ? 'js-view-dom-id-' ~ dom_id,
  ]
%}
<div{{ attributes.addClass(classes) }}>

In the above code, an array of classes is created and added to the HTML attributes.

Whitespace Control

The {% spaceless %} tag removes whitespace between HTML tags. Wrap your code in this tag wherever you want to remove whitespace.

Here’s an example from the Devel contrib module:

{% spaceless %}
  <div class="sf-toolbar-info-piece">
    <b>{{ 'Status'|t }}</b>
    <span class="sf-toolbar-status sf-toolbar-status-{{ request_status_code_color }}">{{ collector.statuscode }}</span> {{ collector.statustext }}
  </div>
  <div class="sf-toolbar-info-piece">
    <b>{{ 'Controller'|t }}</b>
    {{ request_handler }}
  </div>
  <div class="sf-toolbar-info-piece">
    <b>{{ 'Route name'|t }}</b>
    <span>{{ request_route }}</span>
  </div>
{% endspaceless %}

The whitespace control character (-) removes whitespace at the tag level. Here’s an example from Drupal 8 core:

<span{{ attributes }}>
  {%- for item in items -%}
    {{ item.content }}
  {%- endfor -%}
</span>

In the above code, we can see the - character added to the delimiters, indicating the whitespace should be removed. This can also be used to remove the space from both sides or just one, if the character is only on one side.

Caveat regarding newlines at the end of files

Drupal coding standards require that all files have a newline at the end of files, and if you have PHP codesniffer or any other tests set up for Drupal standards, it will require this. However, in Twig, this may not be wanted in your template output. Until a better community-wide solution is reached, you can alter your tests if you need them to pass, or add a twig template tag to the end of the file - you can read this issue for more clarification, as it is an ongoing issue.

Filters

A filter in twig uses the pipe character - |. In our post on the t() function, we talked about using the new |t filter to translate text, but there are other filters that you can use as well. Here's an example from Drupal 8 core:

<li class="project-update__release-notes-link">
  <a href="{{ version.release_link }}">{{ 'Release notes'|t }}</a>
</li>

In the above code, we can see a simple |t filter. The text 'Release notes' is being piped through and is now available for translation, as well as being escaped and safely processed.

There are a variety of Twig filters and Drupal-specific filters.

Here's an example of a Twig filter, join, from Drupal 8 core:

<div class="sf-toolbar-info-piece">
  <b>{{ 'Roles'|t }}</b>
  <span>{{ collector.roles|join(', ') }}</span>
</div>

In the above code, the join filter is applied to collector.roles. It concatenates the items and separates them with a comma and space, as indicated.

Comments

Comments are wrapped in the Twig comment indicator, {# ... #}. Short and long comments use the same indicator, but long comments should be wrapped so that they do not exceed 80 characters per line. Comments on one line should have the indicator on the same line. Comments that span several lines should have the indicators on separate lines. Here's an example of a short comment from Drupal 8 core, the Field UI module:

{# Add Ajax wrapper. #}

Here's an example of a long comment from Drupal 8 core, the Book module:

    {#
      The given node is embedded to its absolute depth in a top level section.
      For example, a child node with depth 2 in the hierarchy is contained in
      (otherwise empty) div elements corresponding to depth 0 and depth 1. This
      is intended to support WYSIWYG output - e.g., level 3 sections always look
      like level 3 sections, no matter their depth relative to the node selected
      to be exported as printer-friendly HTML.
    #}

Syntax

These standards are taken from the Twig Documentation.

1. Put one (and only one) space after the start of a delimiter ({{, {%, and {#) and before the end of a delimiter (}}, %}, and #}). 1a. When using the whitespace control character, do not put any spaces between it and the delimiter.

2. Put one (and only one) space before and after the following operators: comparison operators (==, !=, <, >, >=, <=), math operators (+,-, /, *, %, //, **), logic operators (not, and, or), ~, is, in, and the ternary operator (?:).

3. Put one (and only one) space after the : sign in hashes and , in arrays and hashes.

4. Do not put any spaces after an opening parenthesis and before a closing parenthesis in expressions.

5. Do not put any spaces before and after string delimiters.

6. Do not put any spaces before and after the following operators: |, ., .., [].

7. Do not put any spaces before and after the parenthesis used for filter and function calls.

8. Do not put any spaces before and after the opening and the closing of arrays and hashes.

9. Use lowercase and underscored variable names (not camel case).

10. Indent your code inside tags using two spaces, as throughout Drupal.

Syntax Examples

Here’s an example from Drupal 8 core demonstrating #1a:

<article{{ attributes }}>
  {% if content %}
    {{- content -}}
  {% endif %}
</article>

In the above code, on line 3, you can see that there is no space between the delimiter, {{, and the whitespace control character, -.

Here’s an example from the Display Suite contrib module demonstrating #2 and #4:

{% set left = left|render %}
{% set middle = middle|render %}
{% set right = right|render %}
{% if (left and not right) or (right and not left) %}
  {% set layout_class = 'group-one-sidebar' %}
{% elseif (left and right) %}
  {% set layout_class = 'group-two-sidebars' %}
{% elseif (left) %}
  {% set layout_class = 'group-sidebar-left' %}
{% elseif (right) %}
  {% set layout_class = 'group-sidebar-right' %}
{% endif %}

In the above code, you can see that all of the comparison operators throughout the snippet have one space on either side. You can also see that the expressions, like (left and not right) do not have any spaces after their opening parenthesis or before their closing parenthesis.

Here's an example from Drupal core, the Classy theme, demonstrating #3, #8:

{%
  set row_classes = [
    not no_striping ? cycle(['odd', 'even'], loop.index0),
  ]
%}

In the above code, you can see that there are no spaces before or after the opening or closing of the array (['odd', 'even']), and there is only one space after the comma in the array.

Here’s an example from the Ctools contrib module demonstrating#1, #5, #6, #9, #10:

{% if trail %}
  <div class="wizard-trail">
    {% for key, value in trail %}
      {% if key is same as(step) %}
        <strong>{{ value }}</strong>
      {% else %}
        {{ value }}
      {% endif %}
      {% if value is not same as(trail|last) %}
        {{ divider }}
      {% endif %}
    {% endfor %}
  </div>
{% endif %}

In the above code, you can see that there is only one space after the start of the delimiter, {% and before the end of the delimiter, %}, throughout the code snippet.

On line 7, you can see that there is no space before or after string delimiters {{ .. }}.

On line 9, you can see that there is no space on either side of the pipe operator.

Lower-case variable names are used throughout the snipped, as well as proper indentation.

Here’s an example from the Devel contrib module demonstrating #7:

    <div class="sf-toolbar-block">
        <div class="sf-toolbar-icon">{{ icon|default('') }}</div>
        <div class="sf-toolbar-info">{{ text|default('') }}</div>
    </div>

In the above code, you can see that there are no spaces on either sides of the parentheses. The space after is technically the space before the end of the delimiter.

Wrapping Up

Twig is fairly new to many of us in the Drupal community, so let us know what you think! Once you get the hang of it, it really simplifies a lot of things. Have questions/comments/concerns? Experience you’d like to share? Talk to us on Twitter: @ChromaticHQ!

If you stuck with us through all 7 Code Standards posts, let us know what you thought! We hope they were helpful for new and experienced coders alike. This is all we have planned in this series - for now - but there's always more great content coming!

Drupal 8 Twig Code Standards
Alanna Burke Headshot

Alanna's love of content management systems started with Wordpress while working at Temple University, and continued with Drupal at Saint Joseph’s University, then on to Chromatic.