Our Drupal developers will continue sharing Drupal 8 tips with you. We have published articles so far about configuration in Drupal 8, general tips on Drupal 8 development, and on using Twig in Drupal 8. Today’s article, like the last one, will focus specifically on Twig. Let’s delve into the clear and precise world of developers’ ideas.
Drupal 8 is gaining popularity ever since its first beta version appeared. It has brought with it many changes, particularly in regards to theming. Themes in Drupal 7 and Drupal 8 differ significantly. Here’s a link to these differences. Overall, .info files are replaced with .yml files, as in the modules. The theming functions are also replaced with Twig code and are now run via template files.
Note: as Drupal 8 is changing rapidly, some parts of this article may become outdated quickly.
Creating a theme in Drupal 8
You can create a theme from scratch or you can also use a ready-made theme as a basis and create your own theme as a subtheme. A subtheme differs from the main theme because it inherits the resources of the parent theme. You can create chains of subthemes, each of which will inherit the parent theme resources. This enables you to use ready-made elements instead of creating custom ones (such as css or javascript files), by replacing every element that you need with your own one.
You can use the ‘classy’ theme as a basis. It is used as a basis for core themes such as ‘bartik’ and ‘seven’, and provides its subthemes with many classes that help output the markup elements to the page.
The main theme file is the .info.yml file, which sets all the basic information. This is the replacement of Drupal 7 .info files. Their use gives more opportunities and flexibility in the theme customization. Similar files are created for modules, so it is important to set the ‘theme’ value for the ‘type’ key. A subtheme is created just like any theme, just by adding a ‘base theme’ key.
The .info.yml file must be in the folder with the theme and carry it a name (such my_theme/my_theme.info.yml). Here’s an example of such a file:
name: My theme type: theme description: 'A custom theme based on Classy.' package: Custom base theme: classy core: 8.x version: 8.x-1.0 screenshot: my_theme.png libraries: - my_theme/globalstyling stylesheets-remove: - '@classy/css/layout.css' - core/assets/vendor/normalize-css/normalize.css regions: header: Header content: Content sidebar_first: 'Sidebar first' footer: Footer
The following keys provide meta information about our theme and define its basic functionality:
name (required) - a human-readable theme name that will be displayed in the list of themes on the Appearance page;
description (required) - a description that will be displayed on the Appearance page;
package - the name of the group where the themes with the same key value will be grouped;
type (required) - the type of extension which will always have the ‘theme’ value;
base theme - the basic (parent) theme for this one if we create a subtheme;
core (required) - the Drupal version our theme is compatible with;
version - the module version if it is listed on drupal.org;
screenshot - the image (it can be a screenshot) that will appear on the Appearance page. If this key is not specified, then Drupal will search for a file named ‘screenshot.png’ in the folder with the theme to output it;
libraries - the libraries that will contain the css and js files that will be added to all pages. There is a whole article on drupal.org about adding libraries. Briefly, there is one my_theme.libraries.yml type file added to the theme. Something like this is set there:
globalstyling: version: 1.x css: theme: css/style.css: {} css/print.css: { media: print }
It is ultimately rendered as part of the page html code:
<link rel="stylesheet" href="css/style.css" media="all" /> <link rel="stylesheet" href="css/print.css" media="print" />
stylesheets-remove - removes the link to css added by another theme or module. Here you need to specify the full path from the site directory. You can also use the token marked with the @ symbol.
regions - the theme regions. See more about adding regions here.
Breakpoints
Breakpoints are points at which the site changes the layout of elements depending on the screen width using a media query in CSS. Breakpoints are set in the .breakpoints.yml file. When set in the theme, they will be available to various modules or other themes that create functionality that depends on breakpoints. To specify breakpoint settings, you need to create a my_theme.breakpoints.yml type file. Here is an example of this sort of file content:
my_theme.mobile: label: mobile mediaQuery: '' weight: 2 multipliers: - 1x my_theme.narrow: label: narrow mediaQuery: 'all and (min-width: 560px) and (max-width: 850px)' weight: 1 multipliers: - 1x my_theme.wide: label: wide mediaQuery: 'all and (min-width: 851px)' weight: 0 multipliers: - 1x
Each piece declares a breakpoint that is set with a machine name (eg my_theme.mobile, my_theme.narrow, my_theme.wide) and the following parameters:
label - a human-readable name (label) of the breakpoint;
mediaQuery - the media query syntax which sets the minimum and maximum screen width for the breakpoint;
weight - the order of the breakpoints;
multipliers - the supported pixel multipliers.
Multipliers are a measure of the device screen resolution. It is defined as the ratio of the real active device pixel size and the pixel size that does not depend on the device. Available values: 1x, 1.5x, 2x.
More information about breakpoints can be found on drupal.org.
The .theme file
This file contains hook preprocesses that make changes to the theme. This file performs the same role as the template.php in Drupal 7.
Functions for theming and templates
Drupal 8 uses Twig. This is a template engine for PHP and a part of the Symfony framework. All functions of the theme_* type and the *.tpl.php files are replaced with *.html.twig files.
Drupal 8 allows you to override any templates used to generate html markup. If you want to override the template of the core or other theme, you need to add an .html.twig file with the appropriate name to the theme directory. You can copy the file with the code that you want to replace with your own. After that, clean the cache and then edit your file. Drupal will now run your file instead of its own (instead of the core or theme file). Important: You cannot change the Twig files in the core, you need to make copies of these files in your theme or module and change it there.
If you want to make your changes only to some specific pages, you need to add another file (or copy the basic one) and change its name. For example, if you want to make a template for all nodes of the article type, copy the node.html.twig file, name it node--article.html.twig. This new file is the first thing Drupal will search for, and if it fails to find it, it will use the basic one (i.e. node.html.twig). More information about the template naming is here. The process by which Drupal determines the possible names that the template file can use is called the theme hook suggestion. To add or change suggestions, there are three hooks:
hook_theme_suggestions_HOOK(array $variables)
hook_theme_suggestions_alter(array &$suggestions, array $variables, $hook)
hook_theme_suggestions_HOOK_alter(array &$suggestions, array $variables)
Twig template creating fundamentals
In Drupal 8, Twig is used instead of php code for creating theme templates. Twig has its own syntax. The .html.twig files begin with a description of the variables used in this file. Next is the html markup with the code pieces. For example, the code in the .tpl.php file used to look like this in Drupal 7:
<?php if (!empty($page['preface_first'])): ?> <?php print render($page['preface_first']); ?> <?php endif; ?>
It will look like this in Drupal 8:
{% if page.preface_first %} {{ page.preface_first }} {% endif %}
The variables are enclosed in double braces {{ variable }}, the comments are enclosed in braces with the hash {# comment #}, the structures are enclosed in braces with the percent mark {% for item in items %} {% endfor %}.
The templates can also be debugged. For that, you need to first make changes to the sites/default/services.yml file:
parameters: twig.config: debug: true
This gives you access to several things. First, comments with the debugging information are added to the source code, including the link to Twig files that are used:
This will let you use the dump() function to debug the variables in the .html.twig file. As a parameter, you can send the variable that the function will output on the corresponding page (eg {{ dump(page) }}), and you can also run it without the parameters, then the function outputs all available variables:
For the better debugging, you can add several settings:
auto_reload: true
Now Twig templates will be automatically recompiled if their code changes.
cache: false
By default, the Twig templates are compiled and stored in the file system to increase performance. If you disable Twig caching, the templates will be compiled from the code each time they are used. Of course, in live projects, you need to disable the debug mode.
Find out more about working with variables in the templates on drupal.org.
Creating a custom page using templates
Let’s proceed from theory to practice. First, you need to create a new page in your custom module. See here how to create modules in Drupal 8. I named my module after myself, shedow_module (my nickname is Shedow). So here is our page in the shedow_module.routing.yml file:
shedow_module.theming_page: path: '/theming-page/{number}' defaults: _title: 'Shedow theming page' _controller: '\Drupal\shedow_module\Controller\ShedowController::themingPage' requirements: _permission: 'access content'
As the second element in the path, let’s set ‘number’ - any number or word, in order to show how it can be used in the template. In the shedow_module.module file, let’s declare hook_theme:
function shedow_module_theme() { $theme['shedow_module_page_theme'] = array( 'variables' => array('number' => NULL), 'template' => 'shedow-theme-page', ); return $theme; }
Here we set a default value for the ‘number’ variable and specify which template file will be used for outputting this web page (namely, shedow-theme-page.html.twig). And let’s tie our topic page to the page in the controller (the src/Controller/ShedowController.php file):
namespace Drupal\shedow_module\Controller; use Drupal\Core\Controller\ControllerBase; class ShedowController extends ControllerBase { public function __construct() { } public function themingPage($number) { return array( '#theme' => 'shedow_module_page_theme', '#number' => $number, ); } }
Here we declare the controller class, our constructor and the themingPage function, which takes one parameter (a number or a word with an url). The function returns an array, which shows: the theme for the page (the one we declared in hook_theme) and the value of the number variable. Now you just need to create a template file and set there everything you want to be output on the page. In our case, the file must be in the module ‘templates’ directory:
modules/shedow_module/templates/shedow-theme-page.html.twig:
<section> <h1> My theming page </h1> <p><em> This is my page #{{ number }} </em></p> </section>
If you set the second parameter in the url to be ‘19’ (theming-page/19), it will look as follows:
The first line is the title which we set in the shedow_module.routing.yml file. Everything else is rendered from our template, including our number variable. If the debug is enabled, we can see in the source code that our Twig file is being used:
Wrap-up
So the main difference of Drupal 8 theming from the previous versions is Twig. It altered the php-template based theming system used in Drupal 7 and older versions. Twig enables web designers with html/css skills to modify the page markup without being an expert in php. For example, instead of understanding the syntactic differences between multidimensional arrays and objects, and knowing what to use and where, you can just use the {{ foo.bar }} structure that will do the job. All in all, there are many changes you will have to get used to. But the changes are drivers of progress! :)