Pro-tip: Press space bar to progress and it won't skip any slides

Drupal 8 Theming

A thorough crash course

About Me...

Wes Ruvalcaba

  • Front End Dev at Lullabot
  • Worked with Drupal since version 6
  • Markup Snob, CSS & Sass Dork
  • Write custom code, more than repurpose existing

@wesruv

Lullabot

What We'll Go Over

  • File Organization
  • Very Lean Core Markup
  • CEM Class Syntax
  • Twig
  • YML and Beefed up Config Functionality
  • and more!!!

File Organization

Core is in its own folder

  • Drupal
    • core
      • assets
      • config
      • ...
    • modules
    • profiles
    • sites
    • themes

Your stuff is easier to get to!

  • Drupal
    • core
    • modules
      • mymodule
    • profiles
    • sites
    • themes
      • mytheme

Template.php has a new name

 template.php

 mytheme.theme

The theme's name with a ".theme" extension.

drupal.org/node/2349803

The Info File has a new name

 mytheme.info

 mytheme.info.yml

The theme's name with a ".info.yml" extension.

drupal.org/node/2349803

CEM Class Syntax

aka (BEM)

drupal.org/coding-standards/css/architecture

The Rules

  • We use classes; ID's are the devil
  • Components (The C in CEM) are a chunk of markup that works together;
    e.g. a menu, a teaser, a who's online widget, a social share widget, an article.
  • An element (the E in CEM) is a piece of the whole.
    e.g. a title, some text, an image, call to action link.
  • A modifier is a suffix that can denote a slightly different version of a Component or Element
  • Names are '-' delimited (this goes for a C, E or M)
    .component-name {}
  • Element classes have the component's name, then their name with a double underscore separator
    .component-name__element-name {}
  • Modifiers are added to the end of a component or an element and are separated by a dash.
    
    .component-name--modifier-name {}
    .component-name__element-name--modifier-name {}
div.menu__wrapper {}
  nav.menu {}
    ul.menu__list {}
      li.menu__list-item {}
        a.menu__link {}
      li.menu__list-item {}
        a.menu__link.menu__link--active {}
      li.menu__list-item {}
        a.menu__link {}
          ul.menu.menu--sub-menu {}
            .menu__list {}

JS Prefix

If a class is being used by or is coming from Javascript, it gets a "js-" prefix.


.js-open {}
.js-accordion {}
.js-best-thing-you-ever-saw {}

Pro-tip*: Utility classes


.u-button {}
.u-clearfix {}
.u-text-replace {}
.u-element-invisible {}

* This is something we do at Lullabot on our BEM projects, and not something core is doing.

Very Lean Core Markup and Styles

This is D8 with no theme CSS

A very bare looking web site

Core's Base Themes

Classy

  • Base theme for Seven and Bartik.
  • Has default theming D7 had in core.

Stable

  • Has bare minimum markup/styles needed for things to function.

Both have well organized files, and all of the templates from core.

Classy
?
⊙﹏⊙
Stable

Starting your theming project, which is right for you?

Classy
!
´◕ ᴗ ◕`
Stable

Liked D7's default markup and styles?

Classy is the base theme for you.
Classy
!
´◕ ᴗ ◕`
Stable

Frustrated by D7 defaults and want more control?

Use Stable

OR

Classy
!
´³`
Stable

Want the latest and greatest from core markup? Don't mind keeping up with markup updates?

You can have no base theme...

Ride the roller coaster!

In your *.info.yml add:

base theme: false

Core markup and classes could change and could effect your site.

Twig Basics


{# block.html.twig #}
<div{{ attributes }}>
  {{ title_prefix }}
  {% if label %}
    <h2{{ title_attributes }}>{{ label }}</h2>
  {% endif %}
  {{ title_suffix }}
  {% block content %}
    {{ content }}
  {% endblock %}
</div>
  

Basic Twig Syntax

Print something

Hi {{ name }}

Run code

{% set class = 'my-cool-class' %}

Comment

{# My awesome comment #}

Drilling for Data in Templates

D7 vs. D8

Simple, Static Data

Drupal 7

$title
$content['field_body']
$myvar['has']->alot['of']['nesting']['und'][0]->safe_value

Drupal 8

Dot notation

{{ title }}
{{ content.field_body }}
{{ myvar.has.alot.of.nesting.safe_value }}

Tricksy Datas

Drupal 7

$my_array[$index]
$element['#id']

Drupal 8

{# Since I want to use a variable called index #}
{{ my_array[index] }}

{# Since this index starts with a hash #}
{{ element['#id'] }}

Behind the scenes:


/**
 * If you have {{ sandwich.cheese }} in your twig, it checks:
 */
// Array key.
$sandwich['cheese'];
// Object property.
$sandwich->cheese;
// Also works for magic get (provided you implement magic isset).
$sandwich->__isset('cheese'); && $sandwich->__get('cheese');
// Object method.
$sandwich->cheese();
// Object get method convention.
$sandwich->getCheese();
// Object is method convention.
$sandwich->isCheese();
// Method doesn't exist/dynamic method.
$sandwich->__call('cheese');

twig.sensiolabs.org/doc/templates.html#variables

In other words...

{{ what.does.this.do }}

It could be getting data, or performing functionality.

This is helpful because it's more legible and less picky about syntax.

But it can be confusing if you’re not familiar with the code, and don't know which it's doing.

Twig Filters

Filters modify and then return variables,
and are denoted by a pipe | symbol.

Filter strings!

{# Send translatable string through t function #}
{{ 'Translatable String'|t }}

{# Make a string fit to be a HTML Class #}
{{ node.bundle|clean_class }}

{# Get the string length #}
{% set title_length = title|length %}

There are plenty more:
twig.sensiolabs.org/doc/filters/index.html
drupal.org/node/2357633

Apply a string filter to a block of text


  {% filter trim %}
    Trim will eliminate this     weird    white   space
  {% endfilter %}

Filter arrays!


{# Without will omit part of the array,
   this is being used instead of hide()/show() #}
{{ node|without('comments') }}

{# Get the keys of an array (as an array) #}
{% set my_array_keys = my_array|keys %}

{# Reverse the values #}
{% set countdown = [1, 2, 3, 4, 5]|reverse %}

There are plenty more: twig.sensiolabs.org/doc/filters/index.html

If/Else


{% if not page %}
  {# Stuff #}
{% endif %}

{% if not page %}
  {# Stuff #}
{% else if page and awesome %}
  {# Awesome Stuff #}
{% else %}
  {# Other Stuff #}
{% endif %}

twig.sensiolabs.org/doc/tags/if.html

For Loops

{# A for loop that counts to 10 #}
{% for i in 0..10 %}
  This is number {{ i }}!
{% endfor %}
{# A for loop that iterates through an array #}
{% for user in users %}
  User #{{ loop.index }} is {{ user.username }}
{% endfor %}

twig.sensiolabs.org/doc/tags/for.html

YML and Beefed up Config Functionality

Info.yml

# fluffiness.info.yml
name: Fluffiness
type: theme
description: A cuddly theme.
package: Custom
core: 8.x
libraries:
  - fluffiness/global-styling
regions:
  header: Header
  content: Content
  sidebar_first: Sidebar first
  footer: Footer

drupal.org/node/2349827

Creating a sub theme

# fluffiness.info.yml
name: Fluffiness
type: theme
description: A cuddly theme.
package: Custom
core: 8.x
base theme: classy

drupal.org/node/2165673

Libraries.yml

# fluffiness.libraries.yml
global:
  version: 1.x
  css:
    theme:
      css/layout.css: {}
      css/style.css: {}
      css/colors.css: {}
      css/print.css: { media: print }

drupal.org/theme-guide/8/assets

Define Libraries that can consist of JS and CSS

# Further down in fluffiness.libraries.yml
cuddly-slider:
  version: 1.x
  css:
    theme:
      css/cuddly-slider.css: {}
  js:
    js/cuddly-slider.js: {}

drupal.org/theme-guide/8/assets

Declare dependencies on other libraries

# We forgot our jquery dependency!
cuddly-slider:
  version: 1.x
  css:
    theme:
      css/cuddly-slider.css: {}
  js:
    js/cuddly-slider.js: {}
  dependencies:
    - core/jquery

drupal.org/theme-guide/8/assets

Adding Libraries to specific pages

Using Twig

For most use cases, you can tie a library to markup using attach_library()

{{ attach_library('fluffiness/cuddly-slider') }}
<div>Some fluffy markup {{ message }}</div>

drupal.org/node/2216195

Using PHP

In your .theme file you can use PHP to add the library using whatever logic you'd like.

<?php
function fluffiness_preprocess_maintenance_page(&$variables) {
  $variables['#attached']['library'][] = 'fluffiness/cuddly-slider';
}

function fluffiness_preprocess_page(&$variables) {
  if ($variables['fluffiness'] >= 5000) {
    $variables['#attached']['library'][] = 'fluffiness/cuddly-slider';
  }
}

drupal.org/node/2216195

Breakpoints.YML

# fluffiness.breakpoints.yml
fluffiness.mobile:
  label: mobile
  mediaQuery: ''
  weight: 2
  multipliers:
    - 1x
    - 2x
fluffiness.narrow:
  label: narrow
  mediaQuery: 'all and (min-width: 560px) and (max-width: 850px)'
  weight: 1
  multipliers:
    - 1x
    - 2x
fluffiness.wide:
  label: wide
  mediaQuery: 'all and (min-width: 851px)'
  weight: 0
  multipliers:
    - 1x

drupal.org/documentation/modules/breakpoint

Developing on your local

Dumping vars

{# Will dump all vars available to the template #}
{{ dump() }}

{# Or dump a specific variable #}
{{ dump(specific.var) }}

Devel & Kint

At this point, don't use Devel to dump code unless you enable the Devel Kint sub module.

Kint will replace Krumo, and is usable inside of twig.

{# Will dump all vars available to the template #}
{{ kint() }}

{# Or dump a specific variable #}
{{ kint(specific.var) }}

Setup Twig Developer Settings

  1. Find twig.config and change these values (should be in development.services.yml)
    # Will be buried in the file, search for 'twig.config'
      twig.config:
        debug: true
  2. Add a settings.local.php file with:
    <?php
      $settings['cache']['bins']['render'] = 'cache.backend.null';
      $settings['cache']['bins']['dynamic_page_cache'] = 'cache.backend.null';

drupal.org/node/1903374

Template Debug in D8

Once you've turned on twig debug, you'll get this:

<!-- THEME DEBUG -->
<!-- CALL: theme('block') -->
<!-- FILE NAME SUGGESTIONS:
   * block--system.html.twig
   * block--system-menu-block.html.twig
   * block--system-menu-block--tools.html.twig
   * block--bartik-tools.html.twig
   x block.html.twig
-->
<!-- BEGIN OUTPUT from 'core/modules/block/templates/block.html.twig' -->
<div class="block block-system contextual-region block-menu" id="block-bartik-tools" role="navigation">
  <!-- HTML stuff was here -->
</div>
<!-- END OUTPUT from 'core/modules/block/templates/block.html.twig' -->
  

Pro Tip: Template Debug was added to D7.33+

Add this to your settings.php of choice:

$conf['theme_debug'] = TRUE;

Getting into the weeds

Overriding Libraries

Using your theme's *.info.yml

Replace a library or asset

# fluffiness.info.yml cont'd
libraries-override:
  # Replace an entire library.
  core/drupal.collapse: mytheme/collapse

  # Replace an asset with another.
  subtheme/library:
    css:
      theme:
        css/layout.css: css/my-layout.css

drupal.org/node/2497313

Removing an asset or entire library

# OR in fluffiness.info.yml
libraries-override:
  # Remove an asset.
  drupal/dialog:
    css:
      theme:
        dialog.theme.css: false

  # Remove an entire library.
  core/modernizr: false

drupal.org/node/2497313

Extend a library

# fluffiness.info.yml cont'd
# Extend drupal.user: add assets from classy's user libraries.
libraries-extend:
  core/drupal.user:
    - classy/user1
    - classy/user2

drupal.org/node/2497313

Render Types

The render arrays of tomorrow!

Look for #type in render arrays

<?php
$variables['link'] = array(
  '#type' => 'link',
  '#title' => 'Visit Lullabot',
  '#url' => 'https://lullabot.com',
  '#options' => array(
    'class' => array(
      'some-class',
    ),
  ),
);

Modifying Render Elements

Unfortunately, the easiest way I've found is to modify it in the preprocessor.

<?php
/**
 * Implements template_preprocess_menu_local_task().
 */
function frontendrapport_preprocess_menu_local_task(&$variables) {
  $variables['link']['#options']['attributes']['class'][] = 'admin-tabs__link';
}

ALL the theme_suggestions

They're all done in one place, no longer buried in preprocessor code!

hook_theme_suggestions_alter(array &$suggestions, array $variables, $hook)

api.drupal.org docs for theme_suggestions

I see less hook_form_alter() in your future...

Instead of using hook_form_alter() to add a bit of presentation to a form you can suggest a specific template for the form or any of it's fields!

Attributes Object, your new friend!

<div{{ attributes }}>

No extra space needed

{#  ↓ Don't need this space #}
<div {{ attributes }}>

drupal.org/node/2513632

{# Add or Remove Classes #}
<div{{ attributes.addClass('my-class') }}>
<div{{ attributes.removeClass('their-class') }}>

{# Set any attribute #}
<div{{ attributes.setAttribute('id', 'myID') }}>
<div{{ attributes.setAttribute('data-bundle', node.bundle) }}>

{# Remove any attribute #}
<div{{ attributes.removeAttribute('id') }}>

{# hasClass boolean! #}
{% if attribute.hasClass('myClass') %}
 {# do stuff #}
{% endif %}

drupal.org/node/2513632

Attributes methods can be chained

<div{{ attributes.addClass('hello').removeClass('goodbye') }}>

D8 encourages more classes to be set in Twig

{%
  set classes = [
    'node--' ~ node.bundle|clean_class,
    'node--my-custom-modifier',
  ]
%}

  <article{{ attributes.addClass(classes) }}>

In D7 this would have been done in the preprocessor.

Twig Extends

{# cool.html.twig #}
{% block foo %}
  Default content
{% endblock %}

Meanwhile...

{# cool-er.html.twig #}
{% extends "cool.html.twig" %}
{% block foo %}
  Alternate Content
{% endblock %}

twig.sensiolabs.org/doc/tags/extends.html

And you can even...

{# cool-er.html.twig #}
{% extends "cool.html.twig" %}

{% block foo %}
  {{ parent() }}
  Alternate Content
{% endblock %}

Editorial time...

Extends make a tonne of sense for custom Symfony applications...

But in Drupal land they might make code DRYer at the expense of being intuitive.

Questions?

Sources

Drupal 8 Theme System: hook_theme() to Twig template - Cottser and Joel Pittet at DrupalCon LA events.drupal.org/losangeles2015/sessions/drupal-8-theme-system-hooktheme-twig-template

Drupal 8 Theming with <3 - Morten DK at DrupalCon LA events.drupal.org/losangeles2015/sessions/drupal-8-theming

Theming in Drupal 8 - Drupal.org drupal.org/coding-standards/css/architecture

Twig Official Documentation - twig.sensiolabs.org twig.sensiolabs.org/documentation

Drupal 8 Theming Fundamentals - John Hannah
lullabot.com/articles/drupal-8-theming-fundamentals-part-1