Pro-tip: Press space bar to progress and it won't skip any slides
A thorough crash course
template.php
mytheme.theme
The theme's name with a ".theme" extension.
mytheme.info
mytheme.info.yml
The theme's name with a ".info.yml" extension.
aka (BEM)
.component-name {}
.component-name__element-name {}
.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 {}
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 {}
.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.
This is D8 with no theme CSS
Both have well organized files, and all of the templates from core.
Starting your theming project, which is right for you?
Liked D7's default markup and styles?
Classy is the base theme for you.Frustrated by D7 defaults and want more control?
Use StableOR
Want the latest and greatest from core markup? Don't mind keeping up with markup updates?
You can have no base theme...In your *.info.yml
add:
base theme: false
Core markup and classes could change and could effect your site.
{# block.html.twig #}
<div{{ attributes }}>
{{ title_prefix }}
{% if label %}
<h2{{ title_attributes }}>{{ label }}</h2>
{% endif %}
{{ title_suffix }}
{% block content %}
{{ content }}
{% endblock %}
</div>
Print something
Hi {{ name }}
Run code
{% set class = 'my-cool-class' %}
Comment
{# My awesome comment #}
$title
$content['field_body']
$myvar['has']->alot['of']['nesting']['und'][0]->safe_value
Dot notation
{{ title }}
{{ content.field_body }}
{{ myvar.has.alot.of.nesting.safe_value }}
$my_array[$index]
$element['#id']
{# Since I want to use a variable called index #}
{{ my_array[index] }}
{# Since this index starts with a hash #}
{{ element['#id'] }}
/**
* 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');
{{ 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.
Filters modify and then return variables,
and are denoted by a pipe | symbol.
{# 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
{% filter trim %}
Trim will eliminate this weird white space
{% endfilter %}
{# 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 not page %}
{# Stuff #}
{% endif %}
{% if not page %}
{# Stuff #}
{% else if page and awesome %}
{# Awesome Stuff #}
{% else %}
{# Other Stuff #}
{% endif %}
{# 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 %}
# 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
# fluffiness.info.yml
name: Fluffiness
type: theme
description: A cuddly theme.
package: Custom
core: 8.x
base theme: classy
# fluffiness.libraries.yml
global:
version: 1.x
css:
theme:
css/layout.css: {}
css/style.css: {}
css/colors.css: {}
css/print.css: { media: print }
# Further down in fluffiness.libraries.yml
cuddly-slider:
version: 1.x
css:
theme:
css/cuddly-slider.css: {}
js:
js/cuddly-slider.js: {}
# We forgot our jquery dependency!
cuddly-slider:
version: 1.x
css:
theme:
css/cuddly-slider.css: {}
js:
js/cuddly-slider.js: {}
dependencies:
- core/jquery
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>
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';
}
}
# 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
{# Will dump all vars available to the template #}
{{ dump() }}
{# Or dump a specific variable #}
{{ dump(specific.var) }}
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) }}
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
settings.local.php
file with:
<?php
$settings['cache']['bins']['render'] = 'cache.backend.null';
$settings['cache']['bins']['dynamic_page_cache'] = 'cache.backend.null';
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' -->
Add this to your settings.php of choice:
$conf['theme_debug'] = TRUE;
Using your theme's *.info.yml
# 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
# 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
# fluffiness.info.yml cont'd
# Extend drupal.user: add assets from classy's user libraries.
libraries-extend:
core/drupal.user:
- classy/user1
- classy/user2
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',
),
),
);
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';
}
theme_suggestions
They're all done in one place, no longer buried in preprocessor code!
hook_theme_suggestions_alter(array &$suggestions, array $variables, $hook)
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!
<div{{ attributes }}>
No extra space needed
{# ↓ Don't need this space #}
<div {{ attributes }}>
{# 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 %}
<div{{ attributes.addClass('hello').removeClass('goodbye') }}>
{%
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.
{# cool.html.twig #}
{% block foo %}
Default content
{% endblock %}
Meanwhile...
{# cool-er.html.twig #}
{% extends "cool.html.twig" %}
{% block foo %}
Alternate Content
{% endblock %}
{# cool-er.html.twig #}
{% extends "cool.html.twig" %}
{% block foo %}
{{ parent() }}
Alternate Content
{% endblock %}
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.
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