Porting a Contrib Theme to Drupal 8: Get Twig-gy With It

Drupal 8: getting dialed into theming

NOTE, Because of the ever evolving nature of Drupal 8, some of the contents in this post may be out of date.

I've been pretty busy the past several months in my free time porting my drupal contrib theme, Gratis, to Drupal 8. The port is going well and I've got most things working as they did in the Drupal 7 version but the process has involved a radical reworking of theme's architecture in part for two main reasons:

  1. Twig is now the theme engine for Drupal 8, the PHPTemplate theme engine has gone away that's been a staple in Drupal themes for years.

  2. As with most recent Drupal major version changes, the API has changed yet again for Drupal 8 and this has a direct impact on how you do things within a theming context.

Theme templates and Twig

What does all this mean? For one, you can no longer use PHP code in a theme template, it's all about Twig. Twig simplifies the theme layer and is easy to understand. In a Drupal 8 theme, the old template.php file now becomes your theme's .theme file that holds any special PHP code that's then rendered with Twig variables in twig theme templates. Twig syntax is also easy to understand.

So for example in Drupal 7's page.tpl.php, we have basic code that renders a block region:

 <?php if (!empty($page['preface_first'])): ?>
      <?php print render($page['preface_first']); ?>
  <?php endif; ?>

With Twig and Drupal 8, in our page.html.twig template, we can do this as:

{% if page.preface_first %}
    {{ page.preface_first }}
 {% endif %}

So as you can see, there's no more "tipple fips" (.tpl.php files) with Twig. The .html.twig naming convention is now consistent throughout for theme templates. In general, Twig greatly simplifies the way variables are rendered. So where you used to have print render($page['some_var']) or print render($content['some_var']), these now just become {{ page.some_var }} {{ content.some_var }} or simply {{ some_var }} depending on usage and location.

Here are the key file naming conventions that have changed in Drupal 8:

Drupal 7 > Drupal 8

MYTHEME.info > MYTHEME.info.yml

theme's template.php > MYTHEME.theme

template_name.tpl.php > template_name.html.twig

I was surprised that theme-settings.php did not have a name change either as it works hand in hand with what used to be template.php. Not much has changed for theme-settings.php either, it generally works with Drupal 7 code unchanged though I could not find an API page for function system_theme_settings for Drupal 8.

Another big change in templates is that attributes and classes arrays have been combined together into an attribute object. Before you could render global classes and attributes for body, nodes and comments as:

class="<?php print $classes; ?>"<?php print $attributes; ?>

… which if converted verbatim to Twig would be:

class="{{ classes }}"{{ attributes }}

However, that does not quite work, with the new way, it's actually called as:

 class="{{ attributes.class }}" {{ attributes }}

For shorthand, you can simply do:

{{ attributes }}

… without printing classes specifically, this one variable does everything. I prefer the former method as it allows you to tag on your own theme class if you want to such as clearfix or whatever.

Theme info files

The other significant change with regard to Drupal 8 themes is the info file. It's now written in YAML and parsed by the Symfony YAML component. Converting Drupal 7's .info file was again fairly trivial. I had a look at Bartik's implementation and used that as a guide to learn enough for Gratis' conversion. For example what used to be:

; Stylesheets
stylesheets[all][] = css/style.css

would now be:

   - css/style.css

One other issue I ran into while porting Gratis is that there seemed to be a lot of class collisions between the new admin toolbar in Drupal 8 and Gratis. It seems that the toolbar uses some very general classes such as ul.menu for theming. In the end, I simply got more specific with the classes in my theme to avoid some of those collisions but I'm still sorting that out a bit.

In general, I did not find a ton of documentation for Drupal 8 themes, there's just not a lot out there yet so I found myself really digging into to Drupal 8's core files and themes to see how things ticked. PHPStorm, my IDE of choice, came in handy for targeted searches of functions and code. This provided me with huge insight and in a way, i'm really glad that was the case.

API changes

Where converting your theme templates to Twig is fairly trivial, updating custom code from template.php to MYTHEME.theme is not. The mainstay of converting these functions are that some are deprecated so they still work but will go away eventually. Other functions just plain will not work and throw errors as the API has changed for those entirely or there's new or different ones you'll need to substitute. PHPStorm has a nice visual indicator of deprecated functions in Drupal 8, that really comes in handy.

For example drupal_add_js and drupal_add_css are deprecated so you'll need to use the #attached method now for adding any custom JS to your theme. Drupal 8 is also very lean which means it does not load a lot of Javascript by default for anonymous users so you'll need to use hook_library_info now to create some dependancies for loading things like jQuery.once or even drupal.js for that matter. hook_library_info my yet morph into being done in a config YML file so if you thought the API freeze for Drupal 8 was supposed to have been set in July, 2013, think again, it still seems to be a moving target. This makes it much trickier for contrib developers. The best bet is to follow any related issues for those types of things.

In the next part of this article, I'll get more into all these API changes and give some example code of how I converted the old functions.


  • Drupal8
  • Theming
  • Twig
  • Drupal Planet
  • Architecture


Comments or questions about this post?

Feel free to @reply me on Twitter!