Up and Theming with Drupal 8
Drupal 8 is finally here! We’ve been digging into the code and learning how to install D8 in a way that allow us to sync sites and use it for production work. A lot of things have changed, which we covered in our previous article, Up and Running with Drupal 8. The next step is to see what’s changed in the theming layer, installing a basic theme, and working with the new Twig templating system. There’s a good deal to cover, so let’s jump in!
Creating a Theme
The steps for setting up a basic site theme are fairly simple: create a custom/THEMENAME
directory in web/themes
, and then add a THEMENAME.info.yml
file with the following:
<span class="s">name</span><span class="pi">:</span> <span class="s">THEMENAME Theme</span><span class="s">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">D8</span><span class="nv"> </span><span class="s">theme</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">THEMENAME</span><span class="nv"> </span><span class="s">site.'</span><span class="s">package</span><span class="pi">:</span> <span class="s">Custom</span><span class="c1"># base theme: classy</span><span class="s">type</span><span class="pi">:</span> <span class="s">theme</span><span class="s">version</span><span class="pi">:</span> <span class="s">1.0</span><span class="s">core</span><span class="pi">:</span> <span class="s">8.x</span><span class="s">regions</span><span class="pi">:</span> <span class="s">header</span><span class="pi">:</span> <span class="s">Header</span> <span class="s">content</span><span class="pi">:</span> <span class="s">Content</span> <span class="c1"># required!</span> <span class="s">sidebar_first</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Sidebar</span><span class="nv"> </span><span class="s">first'</span> <span class="s">footer</span><span class="pi">:</span> <span class="s">Footer</span>
Then you can enable your theme (administer » themes
) in the interface. Note that uncommenting base theme: classy
will cause you to set Classy as a parent theme. We feel that Classy is great if you want a lot of useful examples, but really clutters up the markup, so use at your own discretion. After rc1, the default theme will be ‘stable,’ and you may want to pull all of the core templates into your theme to ensure you’re working from the latest updated template code.
Also, the theme name must not contain hyphens. So /theme-name/
is invalid (it won’t even show up!), but /theme_name/
is fine.
Now we’ll want to start customizing our theme. Let us say we have a content type called ‘blog’ (machine name: blog
), with a field type called ‘Publish Date’ (machine name: field_publish_date
).
Despite setting the label of field_publish_date
to ‘inline,’ it’s wrapping to a new line due to the fact that it’s a simple, unstyled <div>
.
Worse, it has no classes to specifically style it. Let’s set ourselves some goals:
- Add the inline styling class(s).
- Change the markup for this field, so that we have a class for the label.
- Add CSS to style the label, but ONLY for the ‘Blog’ content type.
The documentation for this seemingly simple task is obfuscated and evolving right now, but we were able to get it working correctly using the following steps:
Step 1: Turn on twig debug mode. We also found it helpful at this point to make a copy of web/sites/example.settings.local.php
in web/sites/default/
and uncomment the following in settings.php
:
if (file_exists(__DIR__ . '/settings.local.php')) { include __DIR__ . '/settings.local.php';}
This will allow you to disable caching during development, which is no longer a simple checkbox in the performance section. Note that disabling caching can be tricky; the drush cr
(cache rebuild) command is the most reliable way to ensure the cache is really cleared. You’ll also have to rebuild the cache at least once after turning caching off, so the new cache settings are applied.
Step 2: Make a custom field template.
In this case, the suggested debug fields are:
<span class="c"><!-- FILE NAME SUGGESTIONS: * field--node--field-publish-date--blog.html.twig * field--node--field-publish-date.html.twig * field--node--blog.html.twig * field--field-publish-date.html.twig * field--datetime.html.twig x field.html.twig--></span><span class="c"><!-- BEGIN OUTPUT from 'core/modules/system/templates/field.html.twig' --></span>
The highlighted line above shows the template currently being used, suggestions for increased specificity, and the file location (core/modules/system/templates/
).
We want to update field_publish_date
globally, so we’ll create a template called field--field-publish-date.html.twig
To do this, we copy field.html.twig
from the core theme (see the ‘BEGIN OUTPUT’ line above for the path), and rename it in our theme’s folder to field--field-publish-date.html.twig
. Now when we reload, we see the following (if your cache is disabled, of course, otherwise drush cr will clear the cache):
<span class="c"><!-- FILE NAME SUGGESTIONS: * field--node--field-publish-date--blog.html.twig * field--node--field-publish-date.html.twig * field--node--blog.html.twig x field--field-publish-date.html.twig * field--datetime.html.twig * field.html.twig--></span><span class="c"><!-- BEGIN OUTPUT from 'themes/custom/THEMENAME/templates/field--field-publish-date.html.twig' --></span>
Now we can begin to update the markup. The relevant code is:
~~~html
{% if label_hidden %}
… (we don’t care about the label_hidden stuff)
{% else %}
<div{{ attributes }}>
<div{{ title_attributes }}>{{ label }}</div>
…
{% endif %}
~~~
To add the inline styling class, we add the following to the top of the template (below the comments):
~~~html
{%
set classes = [
‘field–label-‘ ~ label_display,
]
%}
~~~
And then update the label’s parent div attributes:
before: <div>
after: <div>
Now the correct class is in place, but we see no change yet - because the <div>
isn’t populating any classes. To fix that, we add the following, again at the top of the template:
~~~html
{%
set title_classes = [
‘field__label’,
‘field__publish-date-label’,
label_display == ‘visually_hidden’ ? ‘visually-hidden’,
]
%}
~~~
And update the div:
before: <div></div>
after: <div ></div>
Rebuild the cache (drush cr) and… success! well sort of - we still have to add CSS. Note that we also added a custom class of ‘field__publish-date-label’ in case we want to style it directly.
Step 3: Add a THEMENAME.libraries.yml
file to hold attachment library definitions.
This is pretty simple; it’s a file with the following:
<span class="s">blog</span><span class="pi">:</span> <span class="s">version</span><span class="pi">:</span> <span class="s">1.x</span> <span class="s">css</span><span class="pi">:</span> <span class="s">theme</span><span class="pi">:</span> <span class="s">css/blog.css</span><span class="pi">:</span> <span class="pi">{}</span> <span class="s">js</span><span class="pi">:</span> <span class="s">js/blog.js</span><span class="pi">:</span> <span class="pi">{}</span> <span class="s">dependencies</span><span class="pi">:</span> <span class="pi">-</span> <span class="s">core/jquery</span>
We then add the directories (/css
and /js
) and files (blog.css/js
). We’ve also added a jQuery dependency, just so you can see how that’s done. If we had something simple that could be done with Vanilla JS we could leave it off. Note that this won’t actually do anything until we follow step 4 below.
Step 4: Add a THEMENAME.theme
file to hold theme hooks (this is actually a PHP file, so start it with <?php
).
This is the code that appends the library based on the content type. The trickiest part of this is figuring out the correct format of hook_preprocess_HOOK()
:
function THEMENAME_preprocess_node__blog(<span class="err">&</span>$variables) { $variables['#attached']['library'][] = 'THEMENAME/blog';]
The theme hook format for content types is to use node__MACHINENAME
format - two underscores.
After that, rebuild your cache (drush cr
), and your CSS and JS files should be loading on every instance of that content type, regardless of the page. (full or teaser)
And that’s it! Note that we could have changed the markup in any number of ways to suit our designs, or even make the template specific to the content type as well as the field.
Disclaimer
The post was written at the end of 2015 while Drupal 8 was still in a Release Candidate stage. While some effort will be made to keep the post up-to-date, if it’s after 2016, you should probably add the current year to your Google search, or better yet, check the docs on Drupal.org.