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) <!-<!-<!-* x --> <!-- THEME DEBUG --> THEME HOOK: 'menu__main' --> FILE NAME SUGGESTIONS: menu--main.html.twig 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="&lt;front&gt;">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