Twig for Drupal 8 Development: Twig Templating Part 1 of 2
Twig is a PHP templating engine and the default templating engine for Drupal 8. Part of the Symfony Framework, Twig syntax compiles templates down to plain PHP when rendered in the browser.
Twig offers advanced features like template inheritance, automatic escaping, variable filters and macros to improve development workflow. Drupal 8's Twig templates are "html.twig" templates, which means that you can mix Twig into HTML easier than you could mix PHP and HTML in Drupal 7’s PHP templates.
Syntax in Twig is somewhat different than PHP, with three main kinds of delimiters:
- {{ }} prints the content of variables or expressions. This is the equivalent of PHP’s print or echo.
- {% %} executes "if" statements, variable definitions or for loops.
- {# #} adds template comments that are not rendered in the page.
A very basic Twig structure to print a message using a variable might look like:
{% set message = ‘Welcome to my great website‘ %}
{{ message }}
When starting to work in Twig, one of the first things you’re going to want to do is enable Twig debug and two key development modules.
As with Drupal 7 debugging, you’ll want to enable the Devel module right away. This will give you access to dpm and dsm functions to dig through available variables during debugging and site building. In Drupal 8 however, there is an additional module included in the Devel install called Kint.
Finding variables with Kint
Kint is very useful in Twig templates. You get all the available variables through dpm and dsm, plus all available methods—which is super handy for pulling in pieces from referenced entities. Methods reach can into other entities to pull fields, labels, and other useful information. Methods can also rewrite the variable output to format it in different ways or access array pieces. After you turn on Twig debugging, you can find variables by using {{ kint() }} inside your template.
Devel also has a nice cache clear function that will clear both the site cache and the image cache. Drupal 8 can be difficult when it comes to displaying image styles on a local environment—especially with responsive images. Devel cache clear will get your images displaying properly.
Twig debugging to get template suggestions
Enabling debugging is very easy in Drupal 8. Every D8 site comes with a default.services.yml file in the sites/default folder. Copy this file and rename it to services.yml ( and make sure you either .gitignore it or assume-unchanged, since you don’t want your debugging settings to be pushed to anywhere except your local machine).
In that services.yml file (around line 39) is a parameter called twig.config. Change the debug variable to true, auto_reload to true, and the cache variable to false. Save and clear cache. You should now be able to use your browser’s inspector to tool to see all the possible template suggestions in the code as well as which templates are being used. This is equivalent to the drush vset theme_debug 1 command in D7.
As with Drupal 7, any module or core template changes you make should be defined in a custom template. This is most easily done in Drupal 8 by copying the Twig template you want to change and either naming it more specifically or adding it to a custom folder at the bottom of your theme structure.
When deciding which Twig template to use, Drupal looks at both name specificity and directory hierarchy. For example, if you have a template called field.html.twig in your theme/templates directory, Drupal will use that in place of the core field.html.twig template because /theme/templates is lower in the hierarchy than /core. If you have a copy of that template in a theme/templates/zzz-custom directory in your theme and customize it there, Drupal will choose to use the one in /zzz-custom instead of because it is at the very bottom of the hierarchy—even though both it and the original are in your /theme/templates directory. This is often a handy trick for customizing templates that are defined by default themes like Classy since its not the best idea to rewrite default theme templates in case you need to reference or revert to them later.
To take it one step further, you can have a field.html.twig and field--field-long-text.html.twig. In that case, it makes no difference what directory they are in (as long as they are somewhere in the theme/templates directory). Drupal will use the field.html.twig for every field except fields with the machine name of field_long_text when it will use the template you created with the more specific name.
Twig naming conventions:
There are many different naming combinations for Drupal Twig templates, depending on the entity you are theming and how specific you are in naming the template. Entity hierarchy levels are always separated by two hyphens and words within each level by one hyphen.
For example:
- field.html.twig (global top level for all field entities)
- field--field-long-text.html.twig (second level for all fields with the machine name of long_text_)
- field--field-long-text--page.html.twig (third level for fields with the machine name long text only on the Page content type).
You can use Twig debug to find template names and theme_suggestion hooks to create new template naming structures.
After setting up aD8 site and going into the theme folders for Bartik and Classy you'll notice that there are lot of templates in D8. This is because Drupal 8 really takes advantage of Twig’s template inheritance capabilities. Twig allows templates to be extended, included and embedded into other templates which allows for a very atomic approach to site building. Extends, Include, and Embed are quite similar, but there are some key points to be aware of.
Use Extends to inherit a parent twig template
Extends brings in the parent template (the one being extended) into the child template. No content outside of blocks defined in the parent template will be allowed in the child template. In a parent paragraphs template (paragraph.html.twig) there are some default classes and html structure you'll want to set that will be used in every paragraph bundle you create:
In a child text paragraph bundle (paragraph--text.html.twig) you would extend the parent template (paragraph.html.twig) and switch out the block content so the child gets the classes you want from the parent but uses its own content.
Since the parent template is being inherited with extends, all content in the child template must be inside a block defined in the parent. Content outside of parent blocks (as shown below) will not be allowed and will throw a Twig error on your site.
Use include to insert one template into another
Include also inherits content of a file, but also allows variables from the current template to replace those from the parent template. Include also differs from extends in that template you are including is included directly where the call is placed in the child. Think of it this way—extends is like bringing a parent template into a child template and changing some of the content of the parent in the child. Include brings a child template into a parent by inserting the entire child template into the parent a specific point. You could also insert an entire parent into a child and replace variables but not content.
The biggest advantage of this in my opinion is including templates from outside a Drupal site into a Drupal site or from one entity type to another. We use Pattern Lab as part of our approach to theming but while our Pattern Lab instance lives inside our Drupal theme it is technically outside of the Drupal site structure so it cannot use variables like {{ content.field_long_text }}. It even has a different suffix for files: paragraph.twig instead of paragraph.html.twig.
To get around that we use include instead of extends and use the ‘with’ function to replace variables from Pattern Lab in Drupal. The parent template paragraph--text.twig in Pattern Lab extends the Pattern Lab paragraphs.twig template to get the html structure then adds example {{ text }} variable content defined in a yml file. To get the final Pattern Lab example output.
Then in Drupal, the paragraph--text.html.twig file uses include to bring in the entire content of the Pattern Lab paragraph-text.twig file and replaces the {{ text }} variable with the Drupal output of the long text field in my text paragraph bundle {{ content.field_long_text }}.
The Drupal template uses the Component Libraries module to find the Pattern Lab Twig template and includes all the Pattern Lab structure but uses the Drupal text field for content.
Embed a twig template to replace variables and override parent blocks
Embed combines include and extends. You can embed a template in another template and replace the variables using 'with' and switch out block content from the child template you are embedding into the parent (or vice versa).
An example of this would be creating paragraphs demo content in Pattern Lab and bringing it into Drupal. Since Pattern lLb doesn’t have modules, we have to hand build the paragraph's HTML structure then exclude it in Drupal (since we don’t want the hand-built paragraphs html structure and Drupal’s).
In Pattern Lab the text.twig template gives basically the same HTML structure that the final Drupal paragraph bundle does but uses some dummy text from a yml file.
The Drupal template paragraph--text.html.twig both extends and embeds templates. It extends the Drupal paragraph.html.twig to add bring global classes and html structure into the text bundle template and it embeds the Pattern Lab text.twig template in place of the parent paragraph.html.twig content block while replacing the {{ text }} variable with the Drupal {{ content.field_long_text }} variable. Adding comments to the plwidgetopen and plwidgetclose blocks prevents them from printing the paragraph structure created in Pattern Lab.
With the setup above, you can theme the Drupal text bundle directly in Pattern Lab and have whatever structure is built around the text variable be reflected both there and around the Drupal long text field as well.
Pro Tips:
-
It is often common to get ‘template not defined’ errors when using include, extends, or embed. You need to make sure that either the template you are inheriting is defined in the theme registry through a theme suggestion hook or that you define the correct path in your inheritance delimiter so drupal knows where to look {% include directory ~ '/templates/widgets/text/text.twig' %}.
-
Blocks are your friend. Block structure can be switched out or added to at will with any of the inheritance methods and placeholder blocks can be used in parent templates to allow for later customization. You can use placeholder header and footer blocks in page.html.twig file to allow for custom scripts, libraries and other code to be added to different content types as needed.
Variables are the name of the game in Twig templates. Otherwise, we would just be hard coding the website. Variables can be found using the Kint or Devel modules with the {{ kint() }} or {{ dump() }} commands. Often these commands are extremely memory heavy and can either crash your site or drive you made waiting for it to load—especially when find variables in views or array loops with a lot of items.
Reduce Kint memory timeouts with |keys filter
Since Kint is so memory heavy, you find yourself needing to increase the memory limit for the site. A couple of handy commands, {{ dump(_context|keys) }} and {{ kint(_context|keys) }}, can help to limit the dump content to just variables—excluding all their child arrays and available methods—which will make Kint very fast and solve the memory issue. You can drill down further by choosing a variable and using the |keys filter on that specific variable {{ kint(content|keys) }} to view its child variables.
Often you won’t be able to find the exact variable you need—like when you are trying to get a term name or id from an entity reference on a paragraph or node. In that case you can use Kint to drill into the entity level and use an available method to access those variables. You can't use the |keys filter to get to methods, but you can skip to the deepest variable and go from there. This is also handy for nested paragraphs and media entities.
Set variables in your twig template
Once you have found the variable, you can either print it directly as listed in Kint or set it as a custom variable. You can also assign multiple values to a variable by using brackets [ ] (very useful for classes). Use ~ join plain text (encased in ‘ ’), to Twig syntax to create complex classes like paragraph--type--text.
For example, on a paragraph bundle for creating social media icons that has a taxonomy reference field for account platform, you'll want to be able to pass the platform name to the svg defined in paragraph--social-media.html.twig so that the right icon will be assigned when an editor chooses a platform.
First, define the social media platform name {% set name = content.field_account_platform.0[‘#taxonomy_term’].getName()|clean_class %}. This reaches through the paragraph (content) to look at the account platform field (.field_account_platform) to the first index (.0) then to the taxonomy term ([‘#taxonomy_term’] and uses a method (.getName() -- found in the Kint available method results) to find the term name. Then it uses Twig’s clean_class filter to turn the name to lowercase and replace spaces with hyphens so the output will be something the svg tag will like.
Now create the svg:
Pro Tips:
-
Any Twig variable that does not have a hashtag can be print by using a . in front. For example, content[field_account_platform’] in Kint can be printed as {{ content.field_account_platform }}. Any variable that has a hashtag must be printed using the brackets and hashtag [‘#taxonomy_term’].
-
Variables cannot be passed into incompatible entities without preprocessing or using include to replace them. Variables that are defined in Pattern Lab cannot be used in Drupal’s html.twig templates. Similarly node variables (like the node title) cannot be used in a block template without preprocessing the block to include node variables.
Twig comes with some default filters you can use with your variables and expressions to further control the results. Twig filters are always preceded by a pipe. In the example above, we're using the clean_class filter to make sure that the paragraph bundle and view modes are always lowercase with hyphen spaces when assigning them to the classes variable.
Some favorite filters are:
-
Join joins strings together with the separator you define.
-
Clean_class prepares string for use as a class name.
-
Clean_id works the same as clean_class but for id’s.
-
Date applies custom formatting to a timestamp. This is similar to the format_date filter except you don’t need to define the custom format in your Drupal admin configuration first.
-
Slice extracts a slice of a sequence or string.
-
Trim removes whitespace or defined characters from a string. You can define if you want to trim whitespace from the left or right side. Note: Using hyphens inside your {{ }} delimiter will also trim whitespace. {{variable -}} would trim whitespace from the end of the variable string.
-
Without creates a copy of a variable array and prints it without the children variables you define. For instance, {{ content|without(‘field_long_text’) }} would print all of the content in the array without the long text field.
-
Keys returns keys of an array for when you want to iterate in a for loop.
-
Striptags removes all tags from the rendered output. You may also choose to allow certain tags.
You can add contributed Drupal modules to expand the available default filters or even write your own.
Twig also lets you assign and add to the attribute objects of variables and html structure. As in the classes variable above, this lets you add additional classes and other attributes to your template structure.
In the last section, we learned that that variables cannot be passed into templates for incompatible entities without preprocessing or inclusion. The exception to this would be macros. A macro is basically a predefined, hard-coded thing defined in one template that you can import for use in any other template.
Define macros
Macros can be defined anywhere in your Twig template structure. One strategy is to define them in the template you are going to use them in or in the top-level template of that group. For instance a macro to build a specific block structure may be defined in block.html.twig and imported into templates within that block hierarchy. A better strategy might be to define all of the macros in one file that is not used anywhere on the site. That way you can add variable comments and whatever structure you want to create a master control area and then import the macros from that file to anywhere you need them.
Another great thing about macros is that they can accept arguments and function very similar to Sass mixins.
Import and use your macros
After you define the macro and the allowed arguments, you can then use it in any Twig template by importing it and printing using the {{ ]} delimiter. When importing and using macros, you need to import the template your macro is defined in (which can be _self if it's in the same template). Then you call that imported file and the macro name with your arguments in parenthesis.
Macros are helpful for anything you're going to be repeating a lot in templates, or for changing theming variables on the fly from a single point. You can define text in the macro and skip the arguments to instantly add and change theming classes.
The power of Twig macros with Pattern Lab
A macros.twig file set in Pattern Lab for for a base build could contain a whole list of variables that can be used to instantly change classes on pieces of a site to kick in pre-defined theming —basically your own little NASA control. You can define things in Pattern Lab like svg icon viewport positions based on an svg sprite or a fixed or non-fixed footer—or whatever else you can think of.
Then in the Pattern Lab page.twig, you import the macros file {% import "@base/global/05-macros/macros.twig" as styles %} and use its macro variables as classes.
That Pattern Lab page.html is then extended into Drupal’s page.html.twig so that changing or removing the 'fixed' text in the macros.twig file will go from a fixed to static footer in both Pattern Lab and Drupal instantly. You could just add or remove the class manually right in the Pattern Lab page.twig and achieve the same result, but defining all the macros in one file makes them a lot easier and quick to change. This approach also lets you list them and their predefined and themed classes in Pattern Lab so that anyone could take one look at the stylesheet and know all the possible functionality at a glance.
Twig templating in Drupal 8 is an extremely large topic and there was no way to cover it all in this post. Look for Twig for Drupal 8 Development: Twig Templating Part 2 to be coming soon. Part 2 will cover creating variables with preprocessing, custom templates with theme suggestions, Views and Twig, and the Twig Tweak Module.
Join the Discussion +