Drupal 8 has many new exciting features. And among those is a new approach to themes. Some conventions remain the same, but new methods have been introduced to make a themer's life much easier. In this article I would like to create a theme and walk you through just a couple of features that I find very useful. This is by far not a comprehensive guide to theming so I encourage you to read more on drupal.org and around internet.
Creating your theme
Themes have relocated. Just like modules, themes now live in the root of your Drupal installation. You can find them under themes directory. By convention you can separate themes into contributed and custom by creating themes/contrib and themes/custom folders.
As usual, each theme lives in its own directory. I will name my theme 'example' and place it into themes/custom/example folder.
To let Drupal know about my theme I will need to create an info file with basic information. In Drupal 8 we do this with a .info.yml file since Drupal 8 is now using yaml language when configuring settings. In my case, I will create example.info.yml with the following content:
name: Example theme
description: 'An example D8 theme'
package: Custom
type: theme
version: 1.0
core: 8.x
This is all similar to Drupal 7 info file except it is written in yml.
With this file in place, we have created an independent stand alone custom theme. However, if you wanted to extend already existing theme you can add the following directive to the file:
base theme: classy
Classy is the name of a base theme in Drupal 8 that comes with sensible defaults.
More configuration
Now that we have our theme available to Drupal, let's have a look at what other options can be declared in the info file.
By default, the theme's screenshot (as seen in the user interface) would be read from screenshot.png file. If you have a file with a different extension or it is located somewhere else other than theme's root directory you can use the following syntax to specify the screenshot's location:
screenshot: screenshot.svg
Next we can add stylesheets and javascript libraries. This is one of the features that is completely different from Drupal 7.
Both javascript and css files are now considered 'libraries' and should be declared in a separate file: example.libraries.yml, which should live in the theme's root folder - themes/custom/example/example.libraries.yml
Here is an example of the libraries.yml definition:
global-library:
version: VERSION
js:
js/scripts.js: {}
css:
base:
css/base/styles.css: {}
components:
css/components/components.css: {}
dependency:
- core/drupal
- core/jquery
other-library
version: VERSION
css:
base:
css/other-lib/styles.css: {}
There are 2 libraries defined in this file: global-library and other-library, each with its own css/js files and dependencies. To make one of these libraries globally available (in other words- to make this library load on every page) you will need to include it in example.info.yml in the following way:
libraries:
- example/global-library
To include any particular library on a specific page it should be included via one of the theme's preprocess hooks or directly in a template file using twig's function:
{{ attach_library('example/other-library') }}
The only template that doesn't support this function is html.html.twig.
By the way, all 'preprocess' functions should be placed in the theme's root directory in the example.theme file. This .theme file is a descendent of Drupal 7 template.php.
See the following example further fown in the article:
example_preprocess_page(&$variables)
There is one more useful directive that we could use in info.yml file and this is stylesheets-remove. If for example you don't want the default system theme to include jquery.ui/themes/base/dialog.css, for example, simply add it to the stylesheets-remove directive. To find its full path use browser web tool like FireBug or Chrome's built in inspector.
stylesheets-remove:
- core/assets/vendor/jquery.ui/themes/base/dialog.css
- '@bartik/css/style.css'
To define regions use the following syntax in your info.yml file:
regions:
header: 'Header'
content: 'Content'
sidebar: 'Sidebar'
footer: 'Footer'
These regions will appear on Blocks page and will let you assign blocks to them. But sometimes you want to have a region that should not be assignable from the blocks interface. Let's say you want to populate the region in code rather than through gui. For this there is a regions_hidden directive which hides already defined regions:
regions_hidden:
- sidebar
Templating
Now we've come to the best part when it comes to theming! Drupal 8 has removed most of the theme_*() functions and replaced them with file templates. You will find only a few templates that are defined as 'theme' functions and eventually even those will be moved into template files.
Let's have a look at how we can harness this:
Let's change our page layout and create our regions. The first question would be what template should I override? In Drupal 6 and Drupal 7 we had the Theme developer module. Once enabled, it would show theme suggestions, the currently used theme and some more information about elements on the page. All we had to do is to direct our mouse pointer to the element we wanted to inspect, click on it, and the themer would show all the relevant information in the themer box including suggestions for theme file overrides and suggested templates we could use to override the output.
But how do we do it in Drupal 8?
In Drupal 8, theme suggestions are inserted into the markup as HTML comments. By default, it is disabled and thus isn't visible. To enable theme suggestions you will need to change settings in sites/default/services.yml file. In that file search for the twig.config directive and set debug and cache to true:
twig.config:
debug: true
cache: false
Now go ahead and clear cache with drush cr command and refresh browser. Fire up your browser inspector and navigate to the html tab if you're using Firebug. There, make sure to tick the 'Show comments' option and now look for comments inside the markup. Voila, theme suggestions are right there! (See image).
Point your mouse to the top of the page and find page.html.twig in the commented out theme suggestions markup. You will notice that some file suggestions are prefixed with *
and some with x
. The ones with *
are suggested template overrides and the one with x
is the name of the template currently in use.
On the next line you will see where current template is located in Drupal's file system:
<!-- BEGIN OUTPUT from 'core/modules/system/templates/page.html.twig' -->
To implement our overrides we could create a new template file with the suggested names or with the name of the current template file and place it in the 'templates' subdirectory of our theme. In my case, I chose to override page.html.twig template so I placed my file inside themes/custom/example/templates/page.html.twig. Not to start with an empty file I copy pasted and tweaked content of the current page.html.twig to accommodate my regions.
Twig
You will notice that templates are no longer using php but twig syntax. Twig is a PHP-based compiled templating language. It is part of the Symfony 2 framework and has become the default template engine in Drupal 8.
You can find twig comprehensive twig tutorials online but here is a short list of twig functions:
{{ 'Hello world' }}
is the same as
<?php echo 'Hello world' ?>
{{ variable }}
is equal to
<?php echo $variable ?>
{{ drill.to.array }}
is much better than
<?php echo $variable['where']['is']['my']['und'][0]->data ?>
{{ variable | trim }}
is passing variable through trim filter and is the same as:
<?php trim($variable) ?>
For a full filter list please check online twig tutorials.
{# comment #}
is commented text (and isn't rendered as markup.)
To translate a block of text use the "trans" wrapper:
{% trans %}
{{ author|passthrough }} {{ date|passthrough }}
{% endtrans %}
Or to translate a string you could use t or trans filters:
{{ 'Hello world'|t }}
or
{{ 'Hello world'|trans }}
You can even set variables:
{% set foo = 'bar' %}
is the same as
<?php $foo = 'bar'; ?>
Control statements, such as if statements are written like this:
{% if foo == 'bar %}
{{ 'foo is really bar' }}
{% else %}
{{ 'foo is not bar' }}
{% endif %}
Here is twig's loop syntax:
{{ "There are #{users.length} users in the list:"|t }}
{% for user in users %}
{{ user.name }}
{% if user.first %}
{{ ' - is the first user in the list'|t }}
{% elseif user.last %}
{{ ' - is the last user in the list'|t }}
{% else %}
{{ " - is user number #{user.index} in the list"|t }}
{% endif %}
{% endfor %}
Template variables
How about discovering available variables in the template file?
Well there are 2 approaches here. The first one is using twig's dump() function.
{{ dump() }}
- will output all available variables
{{ dump(title) }}
- will output title variable
{{ dump(context)|keys }}
- will give you names of available variables
Using {{ dump() }}
in some scenarios might cause exhausted memory so the best would be to output a specific variable you want to inspect.
With the second approach I would recommend installing devel and/or kint modules.
With kint to see all available variables simply insert this anywhere in the template file:
{{ kint() }}
To see specific variable use:
{{ kint(title) }}
It is similar to twig's {{ dump() }}
except the output is in a nice collapsible container.
Unfortunately at the moment I couldn't get devel's dpm()
function to work with twig. But if you favour devel you can output variables through preprocess functions which should be written in .theme file. In our case I placed the example_preprocess_page(&$variables){}
function inside example.theme file in the root of theme's directory:
function example_preprocess_page(&$variables) {
dpm($variables);
}
In summary
There you go, my kickstarter guide for themers for Drupal 8.
There is much more to discover when it comes to Drupal 8 theming and I hope there will be even more pleasant discoveries!
My take away is that themers are now in complete control of markup, css and javascript, and we can happily discard the many nested divs with fewer css classes that are everywhere in Drupal 7.
In this article we've overridden the main page template, but you can do the same with pagers, unordered lists, tables and anything else you can think of.
Try it for yourself and spread the word about awesome Drupal 8.