On Github laurelstreng / Drupal-8-Theming
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 {}
* Just a suggestion for BEM projects, and not something core is doing.
When you're using BEM, sometimes you end up having great use cases for a few utility classes, instead of repeating styles. Prefix them with a "u-" so they're easy to spot.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>If you've used Mustache/Handlebars, Angular, Templated in Blogger or Tumblr, this is very similar
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 }}
{% 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 be buried in the file, search for 'twig.config' twig.config: debug: trueAdd a 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
They're all done in one place, no longer buried in preprocessor code!
hook_theme_suggestions_alter(array &$suggestions, array $variables, $hook)
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 %}
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 Hannahlullabot.com/articles/drupal-8-theming-fundamentals-part-1
Drupal 8 As A Responsive Website - Drupalize.me https://drupalize.me/blog/201405/drupal-8-responsive-what-does-mean