Themes and template overrides
A theme is responsible for the look of the site: page markup, styles and regions for blocks. A module decides what to output, and the theme decides how it looks. You create your own theme to change the templates of core and other modules without touching their files.
Where a theme lives
Your own themes live in web/themes/custom. A core script creates a skeleton. Run it from the web folder:
cd web
php core/scripts/drupal generate-theme blog_theme --path themes/custom
cd ..The folder web/themes/custom/blog_theme appears — a copy of the core starter theme with ready templates and styles.
Enabling
vendor/bin/drush theme:install blog_theme
vendor/bin/drush config:set system.theme default blog_theme -yThe first command enables the theme, the second makes it the default one. The same is done in the panel, in the Appearance section. After that the site looks plain, almost without styles — that is expected for a skeleton.
What is inside
| File or folder | Purpose |
|---|---|
blog_theme.info.yml | The theme description: name, base theme, regions, libraries |
blog_theme.libraries.yml | The theme styles and scripts |
blog_theme.theme | PHP functions: preparing variables for templates |
templates/ | Twig templates |
css/, js/ | Style and script files |
In blog_theme.info.yml the theme has type: theme, and the base theme key names the theme whose templates are inherited. The regions key lists the regions blocks are placed into.
Overriding a template
To change markup, put a file with the same name as the original template into the templates folder of the theme. A node is output by the node.html.twig template. Let us make a separate template for articles only:
{# web/themes/custom/blog_theme/templates/content/node--article.html.twig #}
<article{{ attributes.addClass('article') }}>
<h2><a href="{{ url }}">{{ label }}</a></h2>
<div class="article__body">
{{ content }}
</div>
</article>After drush cr articles are output with this markup and other content types with the old one. label is the title, url the address of the node, content its fields, attributes the tag attributes collected by Drupal and modules.
Name suggestions
Drupal goes through template names from the most specific to the general one and takes the first file it finds.
| File | When it applies |
|---|---|
node--5.html.twig | Only the node with number 5 |
node--article--teaser.html.twig | An article in teaser view |
node--article.html.twig | Any article |
node.html.twig | Any node |
page--front.html.twig | The skeleton of the front page |
block--system-branding-block.html.twig | The block with the logo |
Which names fit a particular place on the page is shown by the Twig development mode from the installation lesson: the page source gets comments with the list of suggestions and the path of the file that was used.
The template of your own module is overridden the same way: a blog-card.html.twig file in the templates folder of the theme replaces the template from the blog module.
Preparing variables
A new variable for a template is added by a function in the blog_theme.theme file:
// web/themes/custom/blog_theme/blog_theme.theme
/**
* Implements hook_preprocess_node().
*/
function blog_theme_preprocess_node(array &$variables): void
{
$node = $variables['node'];
$variables['created_date'] = date('d.m.Y', $node->getCreatedTime());
}The node template gets a new variable: {{ created_date }} outputs the creation date. The function name is made of the theme name, the word preprocess and the template name.
Common mistake
Drupal notices a new template file only after drush cr: the list of templates is kept in the cache. Changes inside a file it has already found are visible at once if Twig development mode is on. And check the name: files use hyphens, and a double hyphen separates the refinement — node--article.html.twig, not node_article.html.twig.