Creating a Drupal 8 theme from scratch Devsigner 2015 (Hashtag #devsigner on the internets)
So you wanna build a website
And you want people to like it
Step 1: Make it pretty
Step 2: Don t make it ugly
So
Learn you an HTML!
Learn you a CSS!
Learn you a jquery!
BUT WAIT
THERE S MORE
You re using a CMS? Drupal, you say?
You need a theme
What is a theme?
What is a theme? Themes control appearance Themes don t provide functionality Usually specific to a single site Contains HTML (templates), CSS, Javascript, and a little PHP
What about Drupal 8?
Changes for Drupal 8 Drupal 8 loves abstractions Drupal 8 loves themers and front end developers Kinda Documentation is still somewhat sparse Drupal 8 is still changing
Setup for Drupal 8 Make sure you use newer versions of Drush, 8.x minimum Clear the cache, a lot Disable page cache, and CSS/JS aggregation, these are now enabled by default
Let s get started
Themes now go in themes directory No more sites/all/ themes
Add a themename.info.yml file
What is.yml?
YAML YAML Ain t Markup Language Readable by machines But also readable by humans, which is cool since most front end developers are humans
key: value strings: "Quoted for special characters or spaces" collection: - "Indented with spaces" - "Start with dash (-)" collections: can: "specify key" nested: items: "Work as well" another: "See?" boolean: true or: false
Make a directory for your theme with it s name Add a themename.info.yml file
demo.info.yml name: Demo type: theme description: 'A demo theme.' core: 8.x
Let s install it!
Wow, that looks like the web in like 1996
Want to remove some of Drupal s default stylesheets?
demo.info.yml name: Demo type: theme description: 'A demo theme.' core: 8.x libraries: - demo/global-styling stylesheets-remove: - core/assets/vendor/normalize-css/normalize.css
We need some CSS
No more drupal_add_css() or drupal_add_js()
Libraries
Drupal 8 Libraries Drupal 8 is all about abstraction and reuse, even with front end code Libraries represent external components Libraries can have CSS, Javascript, and dependencies Examples: normalize, html5shiv, jquery, modernizer, backbone, underscore, CKEditor, etc
Libraries are defined in themename.libraries.yml
demo.libraries.yml global-styling: version: 8.x-1.x css: theme: css/styles.css: {}
demo.info.yml name: Demo type: theme description: 'A demo theme.' core: 8.x libraries: - demo/global-styling
Specify additional options, such as media
demo.libraries.yml global-styling: version: 8.x-1.x css: theme: css/styles.css: {} css/print.css: { media: print }
Let s add some Javascript
demo.libraries.yml global-styling: version: 8.x-1.x css: theme: css/styles.css: {} css/print.css: { media: print } js: js/script.js: {} dependencies: - core/jquery - core/drupal
Note the dependencies, jquery is no longer included on every page by default
What else can you do with libraries?
demo.libraries.yml global-styling: version: 8.x-1.x css: theme: css/styles.css: {} landing-pages: version: 8.x-1.x css: theme: css/landing-pages.css: {}
How to include additional libraries? Specify globally in theme info.yml file Add in preprocess function with #attached Add via Twig function Add to render array
demo.info.yml name: Demo type: theme description: 'A demo theme.' core: 8.x libraries: - demo/global-styling - demo/landing-pages
Where do I put preprocess functions?
Wait, Drupal 8 still has preprocess functions????
Adding a themename.theme file
themes demo css print.css styles.css demo.info.yml demo.libraries.yml demo.theme
Think of.theme as a replacement for template.php
OH MY GOD ITS FULL OF PHP
demo.theme <?php /** * @file * Contains preprocess functions for Demo theme. */ /** * Preprocess for node pages. */ function demo_preprocess_node(&$variables) { $variables['#attached']['library'][] = 'demo/landing-pages'; }
Don t forget to rebuild caches when changing theme files
Moving right along
Templates, what are they good for?
Twig
What s so great about Twig Flexible Easy to use Similar to other template systems (jinja2, swig, etc) SECURE
Twig Examples
{# All twig tags use braces #} {# Comment use the hash/pound #} {{ this_tag_prints_output }} {# Tags with percent signs, are control blocks #} {% if variable %} {{ variable }} {% endif %}
{% set variable = 'Special value.' %} {{ variable clean_class }} {# Prints 'special-value' #}
Example filters Part of Twig capitalize, date, escape, first, length, lower, number_format, trim, etc Drupal provided t, trans, passthrough, placeholder, drupal_escape, safe_join, without, clean_class, clean_id, render
{{ 'This text will be translated' t }} <title>{{ head_title safe_join(' ') }}</title> {# Prints out '<title>homepage Demo site</title>' #}
region.html.twig {% set classes = [ 'region', 'region-' ~ region clean_class, ] %} {% if content %} <div{{ attributes.addclass(classes) }}> {{ content }} </div> {% endif %}
Working with Drupal regions
page.html.twig {% if page.sidebar_first %} <aside class="layout-sidebar-first" role="complementary"> {{ page.sidebar_first }} </aside> {% endif %}
Templates are cached!
Fast websites are good, but caching sucks when you re developing
Disable render caching in sites/default/settings.php
sites/default/settings.php /** * Disable CSS and JS aggregation. */ $config['system.performance']['css']['preprocess'] = FALSE; $config['system.performance']['js']['preprocess'] = FALSE; /** * Disable the render cache (this includes the page cache). * * This setting disables the render cache by using the Null cache back-end * defined by the development.services.yml file above. * * Do not use this setting until after the site is installed. */ $settings['cache']['bins']['render'] = 'cache.backend.null';
Disable caching in sites/default/services.yml
sites/default/services.yml parameters: twig.config: # Twig auto-reload: # # Automatically recompile Twig templates whenever the source code changes. # If you don't provide a value for auto_reload, it will be determined # based on the value of debug. # # Not recommended in production environments # @default null auto_reload: null # Twig cache: # # By default, Twig templates will be compiled and stored in the filesystem # to increase performance. Disabling the Twig cache will recompile the # templates from source each time they are used. In most cases the # auto_reload setting above should be enabled rather than disabling the # Twig cache. # # Not recommended in production environments # @default true cache: true
sites/default/services.yml parameters: 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 # http://drupal.org/node/1906392. # # Not recommended in production environments # @default false debug: false
Lets try it
(after clearing the cache, of course)
<!-- THEME DEBUG --> <!-- THEME HOOK: 'menu main' --> <!-- FILE NAME SUGGESTIONS: * menu--main.html.twig x menu.html.twig --> <!-- BEGIN OUTPUT from 'core/modules/system/templates/menu.html.twig' --> <ul class="menu"> <li class="menu-item"> <a href="/" data-drupal-link-system-path="<front>">home</a> </li> </ul> <!-- END OUTPUT from 'core/modules/system/templates/menu.html.twig' -->
Debug features We can see template suggestions We can see what the default or current used template is We can also use the dump() function to inspect variables
themes demo css styles.css demo.info.yml demo.libraries.yml demo.theme templates region.html.twig
templates/region.html.twig {% set classes = [ 'region', 'region-' ~ region clean_class, ] %} {{ dump(attributes) }} {% if content %} <div{{ attributes.addclass(classes) }}> {{ content }} </div> {% endif %}
Guess what?
The existence of a template file is still cached, even with debug enabled :(
templates/node.html.twig <article{{ attributes }}> {{ title_prefix }} {% if not page %} <h2{{ title_attributes }}> <a href="{{ url }}" rel="bookmark">{{ label }}</a> </h2> {% endif %} {{ title_suffix }} {% if display_submitted %} <footer> {{ author_picture }} <div{{ author_attributes }}> {% trans %}Submitted by {{ author_name }} on {{ date }}{% endtrans %} {{ metadata }} </div> </footer> {% endif %} <div{{ content_attributes }}> {{ dump(content) }} {{ content }} </div> </article>
array (size=5) 'field_image' => array (size=2) '#cache' => array (size=3) 'contexts' => array (size=0)... 'tags' => array (size=0)... 'max-age' => int -1 '#weight' => int -1 'body' => array (size=16) '#theme' => string 'field' (length=5) '#title' => string 'Body' (length=4) '#label_display' => string 'hidden' (length=6) '#view_mode' => string 'full' (length=4) '#language' => string 'en' (length=2) '#field_name' => string 'body' (length=4) '#field_type' => string 'text_with_summary' (length=17) '#field_translatable' => boolean true '#entity_type' => string 'node' (length=4) '#bundle' => string 'article' (length=7)...
<div{{ content_attributes }}> <div class="for-the-tags"> {{ content.field_tags }} </div> {{ content without('field_tags') }} </div>
But what variables are defined?
{{ dump() }}
Other fun stuff to do with templates
{{ attach_library('demo/landing-pages') }}
{% import _self as menus %} {# We call a macro which calls itself to render the full tree. @see http://twig.sensiolabs.org/doc/tags/macro.html #} {{ menus.menu_links(items, attributes, 0) }} {% macro menu_links(items, attributes, menu_level) %} {% import _self as menus %} {% if items %} {% if menu_level == 0 %} <ul{{ attributes.addclass('menu') }}> {% else %} <ul class="menu"> {% endif %} {% for item in items %} <li{{ item.attributes }}> {{ link(item.title, item.url) }} {% if item.below %} {{ menus.menu_links(item.below, attributes, menu_level + 1) }} {% endif %} </li> {% endfor %} </ul> {% endif %} {% endmacro %}
So you ve got a handle on templates
You know how to find template names, suggestions, but
<body class="layout-one-sidebar layout-sidebar-first user-logged-in path-frontpage">
Not included by default with Drupal 8
But what if you liked those?
Enter the Classy theme
Classy theme Provides a base theme that just adds classes for other themes to build on Literally only has about 40 lines of CSS No PHP at all But over a hundred templates! Bartik uses Classy as a base theme
What s with the Drupal core CSS files?
</style> <style media="all"> @import url("/core/modules/contextual/css/contextual.toolbar.css?nqlozl"); @import url("/core/modules/editor/css/editor.css?nqlozl"); @import url("/core/modules/filter/css/filter.caption.css?nqlozl"); @import url("/core/modules/ckeditor/css/plugins/drupalimagecaption/ckeditor.drupalimagecaption.css?nqlozl"); @import url("/core/modules/toolbar/css/toolbar.menu.css?nqlozl"); @import url("/core/modules/ckeditor/css/ckeditor.css?nqlozl"); @import url("/core/modules/contextual/css/contextual.theme.css?nqlozl"); @import url("/core/modules/contextual/css/contextual.icons.theme.css?nqlozl"); @import url("/core/assets/vendor/jquery.ui/themes/base/theme.css?nqlozl"); @import url("/core/misc/dialog.theme.css?nqlozl"); @import url("/core/modules/quickedit/css/quickedit.theme.css?nqlozl"); @import url("/core/modules/quickedit/css/quickedit.icons.theme.css?nqlozl"); @import url("/core/themes/seven/css/components/quickedit.css?nqlozl"); @import url("/core/modules/toolbar/css/toolbar.theme.css?nqlozl"); @import url("/core/modules/toolbar/css/toolbar.icons.theme.css?nqlozl"); @import url("/core/modules/user/css/user.icons.theme.css?nqlozl"); @import url("/core/modules/shortcut/css/shortcut.theme.css?nqlozl"); @import url("/core/modules/shortcut/css/shortcut.icons.theme.css?nqlozl"); @import url("/core/modules/filter/css/filter.admin.css?nqlozl"); </style> <style media="all"> @import url("/core/themes/bartik/css/base/elements.css?nqlozl"); @import url("/core/themes/bartik/css/layout.css?nqlozl"); @import url("/core/themes/bartik/css/components/admin.css?nqlozl"); @import url("/core/themes/bartik/css/components/block.css?nqlozl"); @import url("/core/themes/bartik/css/components/book.css?nqlozl"); @import url("/core/themes/bartik/css/components/breadcrumb.css?nqlozl"); @import url("/core/themes/bartik/css/components/captions.css?nqlozl"); @import url("/core/themes/bartik/css/components/comments.css?nqlozl"); @import url("/core/themes/bartik/css/components/content.css?nqlozl"); @import url("/core/themes/bartik/css/components/contextual.css?nqlozl"); @import url("/core/themes/bartik/css/components/dropbutton.component.css?nqlozl"); @import url("/core/themes/bartik/css/components/featured-top.css?nqlozl"); @import url("/core/themes/bartik/css/components/feed-icon.css?nqlozl"); @import url("/core/themes/bartik/css/components/form.css?nqlozl"); @import url("/core/themes/bartik/css/components/forum.css?nqlozl"); @import url("/core/themes/bartik/css/components/header.css?nqlozl"); @import url("/core/themes/bartik/css/components/region-help.css?nqlozl"); @import url("/core/themes/bartik/css/components/item-list.css?nqlozl"); @import url("/core/themes/bartik/css/components/list-group.css?nqlozl"); @import url("/core/themes/bartik/css/components/node-preview.css?nqlozl"); @import url("/core/themes/bartik/css/components/pager.css?nqlozl"); @import url("/core/themes/bartik/css/components/panel.css?nqlozl"); @import url("/core/themes/bartik/css/components/primary-menu.css?nqlozl"); @import url("/core/themes/bartik/css/components/search.css?nqlozl"); @import url("/core/themes/bartik/css/components/search-results.css?nqlozl"); @import url("/core/themes/bartik/css/components/secondary-menu.css?nqlozl"); @import url("/core/themes/bartik/css/components/shortcut.css?nqlozl"); @import url("/core/themes/bartik/css/components/skip-link.css?nqlozl"); @import url("/core/themes/bartik/css/components/sidebar.css?nqlozl"); @import url("/core/themes/bartik/css/components/site-footer.css?nqlozl"); @import url("/core/themes/bartik/css/components/table.css?nqlozl"); </style>
That s a lot of CSS!
Drupal 8 uses SMACSS
Scalable and Modular CSS It s a book! By Jonathan Snook! It s also a workshop! It s a guide for organizing CSS
SMACSS in Drupal 8 Base CSS reset/normalize plus HTML element styling. Layout macro arrangement of a web page, including any grid systems. Component discrete, reusable UI elements. State styles that deal with client-side changes to components. Theme purely visual styling ( look-and-feel ) for a component.
But it s really up to you in your theme
What are you likely to use in your theme? Base Layouts Component overrides Theme styles
css base elements.css components admin.css book.css breadcrumb.css buttons.css comments.css content.css contextual.css form.css forum.css sidebar.css skip-link.css tabs.css views.css layout.css maintenance-page.css print.css
Standards for organizing CSS https://www.drupal.org/node/1887922
Best practices for CSS architecture in Drupal 8 https://www.drupal.org/node/1887918
Thank you! Please evaluate this session http://devsignercon.com/eval
Extras (time permitting)
Breakpoints
Allows you to expose your CSS breakpoints to the Drupal admin UI
demo.breakpoint.yml demo.mobile: label: mobile mediaquery: '' weight: 2 multipliers: - 2x demo.narrow: label: narrow mediaquery: 'all and (min-width: 560px) and (max-width: 850px)' weight: 1 multipliers: - 2x demo.wide: label: wide mediaquery: 'all and (min-width: 851px)' weight: 0 multipliers: - 2x
<picture> <!--[if IE 9]><video style="display: none;"><![endif]--> <source srcset="styles/large/billy_mays.jpg 1x, styles/ large_960x_960_/billy_mays.jpg 2x" media="all and (min-width: 851px)" type="image/jpeg"> <source srcset="styles/medium/billy_mays.jpg 1x, styles/ medium_440x440_/billy_mays.jpg 2x" media="all and (min-width: 560px) and (max-width: 850px)" type="image/jpeg"> <source srcset="styles/thumbnail/billy_mays.jpg 1x" type="image/jpeg"> <!--[if IE 9]></video><![endif]--> <img property="schema:image" srcset="" alt="billy Mays here" typeof="foaf:image" data-pfsrcset="styles/large_960x_960_/ Billy_Mays.jpg" src="styles/large_960x_960_/billy_mays.jpg" style="" width="350"> </picture>
Screenshots
This is ugly
Add a screenshot Make a PNG Make it 588 x 438 Call it screenshot.png BOOM
But really, there are directions https://www.drupal.org/node/647754
Thank you! Please evaluate this session http://devsignercon.com/eval