April 4, 2017

Twig is a powerful templating engine with many useful features. In the context of Drupal, especially when coming from a Drupal 7 way of doing things, it can be easy to overlook many of these features in the interest of just getting the job done quickly.  However, taking the time to learn Twig beyond just printing variables is worthwhile, because it can help you solve common problems.  

In this series, we will explore some of the tools Twig itself provides, within the context of a Drupal 8 theme to improve the quality of the code we write. Each post will identify a common problem, and provide details about Twig concepts that can be used to help solve them.

Part I: Creating Custom Components

Drupal literally throws the kitchen sink at its front end, generating every piece of HTML from a bunch of different sources: templates provided by modules, Render API, Form API, Field Formatters, and more hidden stages like Pre/Post render. Dealing with this in the theme layer has always been, and still is difficult. Twig alone does not and cannot solve this.

Given all of the above, what do you do if you just want to create something new? Something really simple that Drupal does not provide? It’s such a basic concept, yet one that is rarely encountered in custom themes, because the control in this area is weighted toward the backend first. As web developers, we’re taught and encouraged to keep our code DRY, and we want to, but Drupal makes this very difficult. Defining a custom Drupal template that you can reuse throughout your theme requires:

  • General PHP knowledge.
  • A basic understanding of how Drupal hook implementations work.
  • Knowledge of how to implement hook_theme() inside a theme to create a hook.
  • Knowledge of how to invoke that theme hook in practice. 

When faced with a very basic use case: “I have a bit of code that I want to be able to use all over this theme,” you kinda get slapped in the face with the reality of needing to learn a bunch of PHP/Drupal concepts. In prior versions of Drupal, you either learned how to do these things, or threw your hands up in frustration, and proceeded to duplicate code all over the place.

The bad news is, this is still hard in Drupal 8 with Drupal templates. The good news is, with the help of Twig, its native functionality, and the Component Libraries module, in many cases you can sidestep Drupal templates and go with Twig templates instead where it makes sense, and easily begin to weave them into your theme.

As a result of this, we’re finally starting to see some exciting prospects in the front end Drupal community with contributions like Pattern Lab Starter and Emulsify. These themes are essentially toolkits that take advantage of functionality provided by Twig and the Component Libraries module to implement a Drupal specific version of Pattern Lab.

But what if you’re not yet comfortable with, or just prefer not to use Pattern Lab? With smaller projects, and smaller budgets, there simply may not be enough time to learn about and work one of these into your project. In any case, it’s helpful to learn the concepts driving this functionality because you can apply those concepts to create reusable components in your own themes, at a level and pace that makes sense for you.

Component Libraries Module

The Component Libraries module provides the needed functionality to use plain Twig templates in our themes. It’s important to clarify what I mean by Twig template vs Drupal templates, so let’s get that out of the way.

Template Suffix Details
Drupal .html.twig Originates from Drupal in form of theme hook implementation and is tracked in the theme registry. Can be used in the PHP files of modules and themes.
Twig .twig Originates from a theme directory defined in .info.yml as Component Libraries module namespace. Has no theme hook implementation, and isn’t tracked in the theme registry. Cannot be used in PHP files.

To get started, we need to define a namespace and a path to directory where the components will live, within our theme, outside of the templates directory (which is reserved for Drupal templates). This is done in the theme’s .info.yml file. There can be multiple namespaces, and directories, but let’s keep this as simple as possible and use the “components” namespace and directory:

      - components

With that in place, after we clear the cache, creating new Twig templates, or components, is as simple as creating files inside the directory. For example, to create a component template called foo, you simply create a file named /themes/themename/components/foo.twig.

The foo.twig component template is ready to use, and can be used in any theme template via Twig include, extends and embeds tags. I’ll cover each of these tags in more detail in Part II. To use this template from another Twig template, you would use the include tag:

{% include '@components/foo.twig' %}

That’s all there is to it. For the majority of use cases (namely those that don’t involve working with PHP code), this allows you to gain the benefits of a custom theme hook, without PHP or Drupal knowledge.

Creating a Simple Icon Component

Now that we know how to define a Component Library directory, let’s delve deeper into a real life example. One common use case is the implementation of icons.  In this case we’ll go through the details of implementing SVG sprites.  There are different ways to approach the particulars here, and I won’t get into much detail on them, since they’re off topic, but this generally involves:

  • A directory in the theme that contains the icons.svg files, e.g:

  • A step in the build process that transforms the individual files into a single sprite file (here I’m using gulp-svgmin and gulp-svgstore in a pretty standard gulp task):

    gulp.task('img:icons', function() {
      return gulp.src(['img/icons/*.svg'])
        .pipe(rename({ prefix: 'icon-' }))
        .pipe(svgstore({ inlineSvg: true }))
  • Printing the contents of the SVG file on each page of the Drupal site via html.html.twig: 

    {% include active_theme_path() ~ '/img/icons.svg' %}
  • Our custom Twig component template, which we use to print the HTML needed to reference a single icon from the sprite. This file lives in our theme component directory as themes/themename/components/icon.twig:

    <svg class="icon icon--{{ icon }}"><use xlink:href="#icon-{{ icon }}"></use></svg>

For the purpose of this post, let’s start with the 4th bullet point and create the icon template file at themes/themename/components/icon.twig, placing the markup below it inside. Once that is done, the component is complete.

As you can see, it uses a variable, “icon” which serves as the both name of the original icon file without the extension, and the symbol id reference in the sprite.  When using this component template where we want this icon to appear,  we’ll call the template using Twig’s include tag, and set that variable as the icon we want to use:

{% include '@components/icon.twig' with { icon: 'twitter' } %}

The above code will result in the following markup.

<svg class="icon icon-twitter"><use xlink:href="#icon-twitter"></use></svg>

Scratching the Surface

I hope this post was a helpful introduction for how you might approach getting started with creating your own components. The component templates that you create, how you structure them, how you name them, and what you do in them are totally up to you, and there are so many options.

I recommend checking out the Zen theme for more examples of basic components, and how you might organize them with CSS, and also what has been done in the Pattern Lab space, linked near the top of this post.

The ability to do this opens up a ton of possibilities, and the most exciting aspect of it for me, is that it actually gives some real power to themes from an architectural standpoint. This is the first time in the history of Drupal, that we are able to make these kinds of decisions, and I’m excited to see where we take it over the course of the Drupal 8 cycle.

If you enjoyed this post, stay tuned for Part II, where I’ll cover Twig include, block, extends, and embed tags in-depth.

Theo Ballew, and Gabe Guevara contributed to this post.