How to theme
Drupal 8

Setup

Installation

  1. Download the IMD-THEMING package from Canvas
    • https://thomasmore.instructure.com/courses/18890/modules​
  2. Install through Acquia Dev Desktop/MAMP/XAMP/...
    • Import the DB, do not use install.php
  3. Change database settings in 'sites/default/settings.local.php'

Preparation

Create a theme

File structure

  1. Open your Drupal project with a good editor
    1. PHPStorm
    2. Visual Studio
    3. Brackets?
  2. Go to the map /themes
    • All themes are installed in this folder
  3. Create a new folder
    • Choose a sensible name

Picking a name

Each individual theme is contained in a directory named after the theme itself. For example fluffiness/.

 

The name must be all lowercase, start with a letter, and uses an underscore (_) instead of spaces.

This name will be important, a lot of files will have a reference to this name. 

 

Changing the name later can be annoying.

Info File

Create a theme

Look at the examples

Your Drupal installation already contains a number of themes.

 

As example check these out:

  • /core/themes/bartik
  • /core/themes/classy
  • /core/profiles/demo_umami/themes/umami 

Create .info.yml

In our new folder (F.E.: /themes/fluffiness) add a new file. Name this file:

<themename.info.yml>

 

 

F.E.:

  • fluffiness.info.yml
  • d8theming.info.yml

 

This name must be the same as the folder name!

Add to info .info.yml

Add some variables to your own info file:

name: Calypso
type: theme
base theme: classy
description: 'A theme in dedication to the goddess of the sea.'
core_version_requirement: ^8 || ^9

Activate the new theme

Go to your site, to the appearance page:

  • localhost/drupal/admin/appearance

 

Find your newly created theme and hit Install & set as default

Success?

If all went well your theme should be active, and your site will look like this:

Docs

Adding libraries

Create a theme

Adding CSS

In your theme, add a new folder: css

/themes/fluffiness/css

 

In this folder, create a new file: styles.css

/themes/fluffiness/css/styles.css

Add .libraries.yml

In your theme, add a new file: <themename>.libraries.yml

/themes/fluffiness/fluffiness.libraries.yml

 

Look at the classy.libraries.yml file

Copy the first 4 lines to your library file:

global:
  version: VERSION
  css:
    component:
      css/styles.css: {}

Rename base.css

to styles.css

Link .libraries.yml

In your theme's info.yml file, add the following code:

libraries:
- fluffiness/global

Add some CSS

Add some CSS to your css/styles.css file for testing

 

F.E.:

 

 

 

 

 

Clear caches, refresh home & check result.

body {
    background-color: hotpink;
}

Notes

Whats with the component line in the library?

 

https://www.drupal.org/node/1887922

Docs

Adding Assets

Create a theme

Adding Assets

Want to use assets for you CSS?

Add a new folder: images

/themes/fluffiness/assets

 

Add theme-assets to this folder:

  • Images: assets/images
  • Video: assets/video
  • Fonts: assets/fonts
  • etc

 

 

Adding Assets

You can these use these assets in your theme CSS:

 

background: url('/themes/fluffiness/assets/images/cat.jpg')

Adding Logo

Create a theme

Adding a logo.svg

You can add your own .svg logo to the theme, just place it like this:

 

 

 

 

 

 

 

This logo will automatically be used

Adding a logo.png

Want a non-svg logo? Add it through:

/admin/appearance/settings/d8theming

 

 

Other ways

Adding Screenshot

Create a theme

Adding a screenshot

Add a screenshot.png/.gif file to the theme:

 

 

 

 

 

 

This logo will automatically be used

Twig

Templates

Behind the hood

Example

{# region.html.twig
/**
 * @file
 * Theme override to display a region.
 *
 * Available variables:
 * - content: The content for this region, typically blocks.
 * - attributes: HTML attributes for the region <div>.
 * - region: The name of the region variable as defined in the theme's
 *   .info.yml file.
 *
 * @see template_preprocess_region()
 */
#}
{%
  set classes = [
    'region',
    'region-' ~ region|clean_class,
  ]
%}
{% if content %}
  <div{{ attributes.addClass(classes) }}>
    {{ content }}
  </div>
{% endif %}

Twig code

{# comment #}

{{ variable }}

{% functional %}

Templates

Copy templates

Exercise

  1. Copy over the page template from the base theme
  2. Add a 'container' class
.container {
	max-width: 1200px;
    margin: 0 auto;
}

Exercise

  1. Add a new region to your theme's info file
    • Look at bartik.info.yml
  2. Add this new region to your page template
  3. Add a block to your new region through structure -> blocks

 

Docs: https://www.drupal.org/docs/theming-drupal/adding-regions-to-a-theme

Note: if this exercise is too hard, move on to the next ones

services.yml

copy default.services.yml

Set debug: true

  twig.config:
    # Twig debugging:
    #
    # When debugging is enabled:
    # - The markup of each Twig template is surrounded by HTML comments that
    #   contain theming information, such as template file name suggestions.
    # - Note that this debugging markup will cause automated tests that directly
    #   check rendered HTML to fail. When running automated tests, 'debug'
    #   should be set to FALSE.
    # - The dump() function can be used in Twig templates to output information
    #   about template variables.
    # - Twig templates are automatically recompiled whenever the source code
    #   changes (see auto_reload below).
    #
    # For more information about debugging Twig templates, see
    # https://www.drupal.org/node/1906392.
    #
    # Not recommended in production environments
    # @default false
    debug: false

Clear cache & Refresh!

Naming templates

Naming conventions

 

field.html.twig

  • field--node--field-intro--article.html.twig
  • field--node--field-intro.html.twig
  • field--node--article.html.twig
  • field--field-intro.html.twig
  • field--text-long.html.twig

Naming templates

html.html.twig

  • html--internalviewpath.html.twig
  • html--node--id.html.twig
  • html.html.twig

 

page.html.twig

  • page--node--edit.html.twig
  • page--node--1.html.twig
  • page--node.html.twig
  • page.html.twig

Which templates

 

  • templates/a/node.html.twig
  • templates/z/node.html.Twig

 

  • templates/a/node.html.twig
  • templates/a/node-foo.html.Twig

Filters

{% filter upper %}
    uppercase is awesome
{% endfilter %}
{{ variable|upper }}

Hardcoded text

Drupal variable

More filters

{{ var|clean_class }}
{{ var|clean_id }}
{{ var|format_date }}
{{ var|raw }}
{{ var|render }}
{{ var|safe_join }}
{{ var|without }}

Drupal only filters

<a 
    href="{{ url('<front>') }}" 
    title="{{ 'Home'|t }}" 
    rel="home" 
    class="site-logo"
></a>

Translation

{% trans %}Translatable text{% endtrans %}

Without filter

{{ content|without('field') }}

Without filter example

{{ content }}

{{ text }}

{{ tags }}

{{ image }}

{{ content|without('image', 'tags') }}

{{ content|without('image', 'tags') }}

{{ text }}

{{ content.tags }}

{{ content.image }}

In code

Finding field names

Exercise

  1. Go to the home page
    • Or create a new page
  2. Create a template for the paragraph 'Text with Media'
  3. Split the paragraph in 2 areas: left & right
  4. Add 'grid' classes to the markup
.row {
    margin-left: -1rem;
    margin-right: -1rem;
}

.column {
    float: left;
    padding-left: 1rem;
    padding-right: 1rem;
}

@media only screen and (min-width: 768px)  {
    .col-md-6 {
        width: 50%;	
    }
}

Set variables

{%
  set classes = [
    'node',
    'node--type-' ~ node.bundle|clean_class,
    node.isPromoted() ? 'node--promoted',
    node.isSticky() ? 'node--sticky',
    not node.isPublished() ? 'node--unpublished',
    view_mode ? 'node--view-mode-' ~ view_mode|clean_class,
  ]
%}

Debugging vars

{{ dump(var) }}  -> not reliable

{{ dpm(var) }}   -> outputs a lot

{{ kpr(var) }}   -> similar to dpm

{{ kint(var) }}  -> most readable;
                 -> very heavy on memory
                 -> crashes a lot on big objects

Debugging vars

{{ dpm(content) }}

{# Print only field_body information #}
{{ dpm(content.field_body) }}

{# Printing image url value #}
{{ dpm(content.field_bg_image.0.entity.uri.value) }}

{# Printing a media image url value #}
{{ file_url(content.field_media_image|field_target_entity.field_media_image.entity.uri.value) }}

Exercise

  1. Go to the About us page
    • or create a new page
  2. Create a template for the paragraph 'Text with background image'
  3. Rewrite the output of the 'Background image' field as a background image
    • It's ok to use inline CSS
  4. Rewrite the output of the 'Text background color' as a class to style
    • F.E.: 'color-primary', 'color-transparent' 
  5. Style it

Attributes

<div class="nickelback">
<div {{
    attributes
        .removeClass('nickelback')
        .addClass('rush')
        .setAttribute('id', 'top')
    }}>
<div id="top" class="rush">

For HTML attributes

Attributes

.addClass()

.removeClass()

.setAttribute()

.removeAttribute()

.hasClass()

More functions

Libraries

In Drupal 8, stylesheets (CSS) and JavaScript (JS) are loaded through the same system for modules (code) and themes, for everything: asset libraries.

 

 

 

Drupal uses a high-level principle: assets (CSS or JS) are still only loaded if you tell Drupal it should load them.

 

Drupal does not load every asset on every page, because it slows down front-end performance.

 

The process

  1. Save the CSS or JS to a file using the proper naming conventions and file structure.
  2. Define a "library", which can contain both CSS and JS files.
  3. "Attach" the library to
    • all pages,
    • specific Twig templates,
    • a render element in a preprocess function.

The process

global-styling

global-styling:
  version: 1.x
  css:
    theme:
      css/layout.css: {}
      css/style.css: {}
      css/colors.css: {}
      css/print.css: { media: print }

css/print.css: { media: type }

all Suitable for all devices.
aural Intended for speech synthesizers.
braille Intended for braille tactile feedback devices.
embossed Intended for paged braille printers.
handheld Intended for handheld devices (typically small screen, monochrome, limited bandwidth).
print Intended for paged, opaque material and for documents viewed on screen in print preview mode. Please consult the section on paged media.
projection Intended for projected presentations, for example projectors or print to transparencies. Please consult the section on paged media.
screen Intended primarily for color computer screens.
tty Intended for media using a fixed-pitch character grid, such as teletypes, terminals, or portable devices with limited display capabilities.
tv Intended for television-type devices.
 

Multiple declarations

js-header:
  header: true
  js:
    header.js: {}

js-footer:
  js:
    footer.js: {}

in same libraries.yml file

Weight

base:
 foo.css: { weight: - 666 }
js-header:
  header: true
  js:
    header.js: {}

Show in <head>

External

theme:
 http://fonts.googleapis.com/css?
  family=Loved+by+the+King: { type: external }

JavaScript

global-styling:
  version: 1.x
    js:
      js/scripts.js: {}

JavaScript

global-styling:
  version: 1.x
    js:
      js/scripts.js: {}
      /libraries/cycle2/jquery.cycle2.min.js: {}

Place all Downloaded JS/CSS files in a folder /libraries outside of your theme!

Libraries folder

Dependencies

adding jQuery

global-styling:
  version: 1.x
  css:
    base:
      css/d8imd.css: {}
  js:
    js/d8imd.js: {}
  dependencies:
    - core/jquery

More dependencies

  • core/drupal

We need core/drupal in order to take advantage of the Drupal.behaviors.

 

  • core/jquery

To include the Drupal core version of jQuery, we add core/jquery.

 

  • core/jquery.once

A jQuery plugin allowing to only apply a function once to an element.

More dependencies

To get a complete overview of all the core libraries, take a look inside core/core.libraries.yml.

 

Attaching a library

libraries:
  - core/normalize
  - d8imd/global-styling

d8imd.info.yml

Attaching a library

{{ attach_library('d8imd/global-styling') }}

foo.html.twig

{{ attach_library(active-theme()~'/global-styling') }}

Example

block--views-block--carousel-block-home.html.twig

Example

trainingstore_foundaton.libraries.yml

Other example

{% if id == "view_carousel" %}
  {{ attach_library('theme/carousel') }}
{% endif %}

Stylesheets remove

d8imd.info.yml

Docs

Overriding and extending libraries

JavaScript

Where?

Creating .js file

Coding standards

As part of the Drupal 8 Javascript Coding Standards, all of the javascript code must be declared inside a closure wrapping the whole file. This closure must be in strict mode.

(function () {
  'use strict';
  // Custom javascript
})();

Plain JS

(function () {
  'use strict';
  window.alert("sometext");
})();

.js file: using jQuery

.js file: using jQuery

(function () {
  'use strict';

  $( document ).ready(function() {
    console.log( "ready!" );
  });

})();

But we have jQuery included?

.js file: using jQuery

(function ($) {

  'use strict';

  $( document ).ready(function() {
    console.log( "ready!" );
  });

})(jQuery);

Enter Drupal.behaviors

/**
 * @file
 * Placeholder file for custom sub-theme behaviors.
 *
 */
(function ($, Drupal) {

  'use strict';

  /**
   * Example drupal behavior
   */
  Drupal.behaviors.exampleBehavior = {
    attach: function (context, settings) {
      $('.example', context).once('example-behavior').each(function () {

      });
    }
  };

})(jQuery, Drupal);

Why Drupal.behaviors?

Drupal behaviors (Drupal.behaviors) are still part of javascript in core.

 

These behaviors will be executed on every request, including AJAX requests.

Calls multiple times

  • After an administration overlay has been loaded into the page.
  • After the AJAX Form API has submitted a form.
  • When an AJAX request returns a command that modifies the HTML, such as 

 

Can also be triggered from a module!

Behaviors examined

Including Drupal object

/**
 * @file
 * Placeholder file for custom sub-theme behaviors.
 *
 */
(function ($, Drupal) {

})(jQuery, Drupal);

Add Drupal & jQuery.once

Behaviors examined

/**
 * Example drupal behavior
 */
Drupal.behaviors.awesome = {
  attach: function(context, settings) {
    $('main', context).once('awesome').append('<p>Hello world</p>');
  }
};

Behaviors examined

namespace:

 A Drupal behavior has to have a unique namespace.

 

In this example, the namespace is awesome (Drupal.behaviors.awesome).

Behaviors examined

attach:

Contains the actual function that should be executed.

attach: function (context, settings) {
  $('.example', context).once('example-behavior').each(function () {
    alert('I\'m helping!');
  });
}

Behaviors examined

once:

 Using the .once('awesome') will make sure the code only runs once. Otherwise, the code will be executed on every AJAX request.

 

It adds a processed- class to the main tag (<main role="main" class="awesome-processed">) in order to accomplish this (core/jquery.once).

$('main', context).once('awesome').append('<p>Hello world</p>');

Advanced example

  // Engine
  Drupal.sbs_nu_v2.startAnimation = function() {
    Drupal.sbs_nu_v2.animateActiveItem();
    window.setInterval(function(){
      Drupal.sbs_nu_v2.animateActiveItem();
    }, 20000);
  };


  // Init
  Drupal.behaviors.sbs_nu_v2 = {
    attach: function (context, settings) {
      $('#sbs-nu-v2', context).once('init-epg-box', function () {
        Drupal.sbs_nu_v2.container = $(this);
        Drupal.sbs_nu_v2.init();
      });
    }
  };

Multilingual

Use Drupal.t()

// Make string available on translate interface
var close = Drupal.t('Close');

// Same, but with @placeholder 
var nodesOfType = Drupal.t('Showing nodes of @type', {@type: nodeType});

Debugging behaviors?

Set breakpoint in devtools

misc/drupal.js

Debugging behaviors?

Use console.log();

console.log('Hi');
console.log(Drupal);

Docs

Exercise: mobile menu toggle

  1. Add a JavaScript Behavior (using jQuery) that:
    • Adds a toggle button (hamburger icon) to the menu
    • Make the menu open/close on click 

Tips:

.theme file

.theme

For what?

You can affect the output of certain HTML via preprocess functions.

 

For example, if you wanted to add a class to a menu and preferred to do this at the PHP level you can.

How?

  1. Create a file in your theme directory called mytheme.theme
     
  2. Create a function such as mytheme_preprocess_HOOK where HOOK refers to the item you wish to affect.
     
  3. Write your changes and save
     
  4. Rebuild the cache so your changes are available

Start file

Add "<?php" opening-tag

 

 

 

 

 

 

 

 

 

Closing tag not needed

<?php
/**
 * @file
 * template.php
 */
 
// Add these when/if needed
use Drupal\Component\Utility\Html;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\media\Entity\Media;

// add functions below

Example

Add class my-menu to all menus

/**
* Implements hook_preprocess_HOOK() for menu.html.twig.
*/
function mytheme_preprocess_menu(&$variables) {
  // If there is not an existing class array, create an empty array.
  if (!isset($variables['attributes']['class'])) {
    $variables['attributes']['class'] = [];
  }
  // Merge with any classes that may have been set
  // by other hook_preprocess_menu invocations
  $variables['attributes']['class'] = 
           array_merge($variables['attributes']['class'], ['my-menu']);
}

Debugging

Use var_dump(), dsm(), kpr(), kint() or XDebug

/**
* Implements hook_preprocess_HOOK() for menu.html.twig.
*/
function mytheme_preprocess_menu(&$variables) {

  var_dump($variables);
  var_dump($variables['main_menu']);

  dsm($variables);
}

Hooks?

Use twig debug; see theme suggestions

Read the docs

Example

Add class my-main-menu to the main menu

/**
* Implements hook_preprocess_HOOK() for menu.html.twig.
*/
function mytheme_preprocess_menu(&$variables) {
  if ($variables['menu_name'] == 'main') {
    if (!isset($variables['attributes']['class'])) {
      $variables['attributes']['class'] = [];
    }
    $variables['attributes']['class'] = 
      array_merge($variables['attributes']['class'], ['my-main-menu']);  }
}

Another example

Add page link as a var: url

/**
* Implements hook_preprocess_node().
*/
function mytheme_preprocess_node(&$variables) {

  if(isset($variables['content']['#node'])) {
    $node = $variables['content']['#node'];

    // Add node url
    $variables['url'] = $node->toUrl();
  }

}

in node.html.twig template:

<a href="{{ url }}" class="inner">

Advanced example

Add some classes to our paragraphs

/**
 * Implements hook_preprocess_paragraph().
 */
function mytheme_preprocess_paragraph(&$variables) {

  if ($entity = $variables['elements']['#paragraph']) {
    
    // Title align.
    if ($entity->hasField('field_title_align')) {
      $field_title_align = $entity->get('field_title_align')->getValue();
      $field_title_align = reset($field_title_align);

      if ($field_title_align) {
        $variables['attributes']['class'][] = 'title-align--' 
        . Html::cleanCssIdentifier(reset($field_title_align));
      }
    }
  }
}

in paragraph.html.twig template:

<section{{ attributes.addClass(classes) }}>
  <div class="container">
    <div class="layout paragraph__layout">

Theme settings

Where?

In Drupal 8, themes can modify the entire theme settings form by adding a PHP function to either the themename.theme file or to a themename-settings.php file.

Example

function foo_form_system_theme_settings_alter(
           &$form,
           \Drupal\Core\Form\FormStateInterface &$form_state,
           $form_id = NULL
) {
  // Work-around for a core bug affecting admin themes. See issue #943212.
  if (isset($form_id)) {
    return;
  }

  $form['foo_example'] = array(
    '#type'          => 'textfield',
    '#title'         => t('Widget'),
    '#default_value' => theme_get_setting('foo_example'),
    '#description'   => t("Place this text in the widget spot on your site."),
  );
}

Add textfield with title Widget 

in themename.theme

Example

foo_example: blue bikeshed

Add config/install/THEME.settings.yml file

Set default value

$foo_example = theme_get_setting('foo_example');

Retrieve value in php

Example

<?php
function foo_preprocess_node(&$variables) {
  $variables['foo_example'] = theme_get_setting('foo_example');
}

Make value available for twig files:

In themename.theme file:

{{ foo_example }}

In twig file:

Docs!

Drupal Theming IMD

By Pieter Mathys

Drupal Theming IMD

How to theme D8

  • 1,079