Create a theme
Each individual theme is contained in a directory named after the theme itself. For example fluffiness/.
The name must be all lowercase, start with a letter, and uses an underscore (_) instead of spaces.
This name will be important, a lot of files will have a reference to this name.
Changing the name later can be annoying.
Create a theme
Your Drupal installation already contains a number of themes.
As example check these out:
In our new folder (F.E.: /themes/fluffiness) add a new file. Name this file:
<themename.info.yml>
F.E.:
This name must be the same as the folder name!
Add some variables to your own info file:
name: Calypso
type: theme
base theme: classy
description: 'A theme in dedication to the goddess of the sea.'
core_version_requirement: ^8 || ^9
Go to your site, to the appearance page:
Find your newly created theme and hit Install & set as default
If all went well your theme should be active, and your site will look like this:
Create a theme
In your theme, add a new folder: css
/themes/fluffiness/css
In this folder, create a new file: styles.css
/themes/fluffiness/css/styles.css
In your theme, add a new file: <themename>.libraries.yml
/themes/fluffiness/fluffiness.libraries.yml
Look at the classy.libraries.yml file
Copy the first 4 lines to your library file:
global:
version: VERSION
css:
component:
css/styles.css: {}
Rename base.css
to styles.css
In your theme's info.yml file, add the following code:
libraries:
- fluffiness/global
Add some CSS to your css/styles.css file for testing
F.E.:
Clear caches, refresh home & check result.
body {
background-color: hotpink;
}
Create a theme
Want to use assets for you CSS?
Add a new folder: images
/themes/fluffiness/assets
Add theme-assets to this folder:
You can these use these assets in your theme CSS:
background: url('/themes/fluffiness/assets/images/cat.jpg')
Create a theme
You can add your own .svg logo to the theme, just place it like this:
This logo will automatically be used
Want a non-svg logo? Add it through:
/admin/appearance/settings/d8theming
Create a theme
Add a screenshot.png/.gif file to the theme:
This logo will automatically be used
Templates
{# region.html.twig
/**
* @file
* Theme override to display a region.
*
* Available variables:
* - content: The content for this region, typically blocks.
* - attributes: HTML attributes for the region <div>.
* - region: The name of the region variable as defined in the theme's
* .info.yml file.
*
* @see template_preprocess_region()
*/
#}
{%
set classes = [
'region',
'region-' ~ region|clean_class,
]
%}
{% if content %}
<div{{ attributes.addClass(classes) }}>
{{ content }}
</div>
{% endif %}
{# comment #}
{{ variable }}
{% functional %}
.container {
max-width: 1200px;
margin: 0 auto;
}
Docs: https://www.drupal.org/docs/theming-drupal/adding-regions-to-a-theme
Note: if this exercise is too hard, move on to the next ones
https://www.drupal.org/node/2358785
https://www.drupal.org/docs/8/theming/twig/debugging-twig-templates
copy default.services.yml
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
# https://www.drupal.org/node/1906392.
#
# Not recommended in production environments
# @default false
debug: false
field.html.twig
html.html.twig
page.html.twig
{% filter upper %}
uppercase is awesome
{% endfilter %}
{{ variable|upper }}
Hardcoded text
Drupal variable
{{ var|clean_class }}
{{ var|clean_id }}
{{ var|format_date }}
{{ var|raw }}
{{ var|render }}
{{ var|safe_join }}
{{ var|without }}
<a
href="{{ url('<front>') }}"
title="{{ 'Home'|t }}"
rel="home"
class="site-logo"
></a>
Translation
{% trans %}Translatable text{% endtrans %}
{{ content|without('field') }}
{{ content }}
{{ text }}
{{ tags }}
{{ image }}
{{ content|without('image', 'tags') }}
{{ text }}
{{ content.tags }}
{{ content.image }}
.row {
margin-left: -1rem;
margin-right: -1rem;
}
.column {
float: left;
padding-left: 1rem;
padding-right: 1rem;
}
@media only screen and (min-width: 768px) {
.col-md-6 {
width: 50%;
}
}
{%
set classes = [
'node',
'node--type-' ~ node.bundle|clean_class,
node.isPromoted() ? 'node--promoted',
node.isSticky() ? 'node--sticky',
not node.isPublished() ? 'node--unpublished',
view_mode ? 'node--view-mode-' ~ view_mode|clean_class,
]
%}
{{ dump(var) }} -> not reliable
{{ dpm(var) }} -> outputs a lot
{{ kpr(var) }} -> similar to dpm
{{ kint(var) }} -> most readable;
-> very heavy on memory
-> crashes a lot on big objects
{{ dpm(content) }}
{# Print only field_body information #}
{{ dpm(content.field_body) }}
{# Printing image url value #}
{{ dpm(content.field_bg_image.0.entity.uri.value) }}
{# Printing a media image url value #}
{{ file_url(content.field_media_image|field_target_entity.field_media_image.entity.uri.value) }}
<div class="nickelback">
<div {{
attributes
.removeClass('nickelback')
.addClass('rush')
.setAttribute('id', 'top')
}}>
<div id="top" class="rush">
For HTML attributes
.addClass()
.removeClass()
.setAttribute()
.removeAttribute()
.hasClass()
More functions
In Drupal 8, stylesheets (CSS) and JavaScript (JS) are loaded through the same system for modules (code) and themes, for everything: asset libraries.
Drupal uses a high-level principle: assets (CSS or JS) are still only loaded if you tell Drupal it should load them.
Drupal does not load every asset on every page, because it slows down front-end performance.
global-styling:
version: 1.x
css:
theme:
css/layout.css: {}
css/style.css: {}
css/colors.css: {}
css/print.css: { media: print }
all | Suitable for all devices. |
aural | Intended for speech synthesizers. |
braille | Intended for braille tactile feedback devices. |
embossed | Intended for paged braille printers. |
handheld | Intended for handheld devices (typically small screen, monochrome, limited bandwidth). |
Intended for paged, opaque material and for documents viewed on screen in print preview mode. Please consult the section on paged media. |
projection | Intended for projected presentations, for example projectors or print to transparencies. Please consult the section on paged media. |
screen | Intended primarily for color computer screens. |
tty | Intended for media using a fixed-pitch character grid, such as teletypes, terminals, or portable devices with limited display capabilities. |
tv |
Intended for television-type devices. |
js-header:
header: true
js:
header.js: {}
js-footer:
js:
footer.js: {}
in same libraries.yml file
base:
foo.css: { weight: - 666 }
js-header:
header: true
js:
header.js: {}
theme:
http://fonts.googleapis.com/css?
family=Loved+by+the+King: { type: external }
global-styling:
version: 1.x
js:
js/scripts.js: {}
global-styling:
version: 1.x
js:
js/scripts.js: {}
/libraries/cycle2/jquery.cycle2.min.js: {}
Place all Downloaded JS/CSS files in a folder /libraries outside of your theme!
adding jQuery
global-styling:
version: 1.x
css:
base:
css/d8imd.css: {}
js:
js/d8imd.js: {}
dependencies:
- core/jquery
We need core/drupal in order to take advantage of the Drupal.behaviors.
To include the Drupal core version of jQuery, we add core/jquery.
A jQuery plugin allowing to only apply a function once to an element.
To get a complete overview of all the core libraries, take a look inside core/core.libraries.yml.
libraries:
- core/normalize
- d8imd/global-styling
d8imd.info.yml
{{ attach_library('d8imd/global-styling') }}
foo.html.twig
{{ attach_library(active-theme()~'/global-styling') }}
block--views-block--carousel-block-home.html.twig
trainingstore_foundaton.libraries.yml
{% if id == "view_carousel" %}
{{ attach_library('theme/carousel') }}
{% endif %}
d8imd.info.yml
Coding standards
As part of the Drupal 8 Javascript Coding Standards, all of the javascript code must be declared inside a closure wrapping the whole file. This closure must be in strict mode.
(function () {
'use strict';
// Custom javascript
})();
(function () {
'use strict';
window.alert("sometext");
})();
(function () {
'use strict';
$( document ).ready(function() {
console.log( "ready!" );
});
})();
But we have jQuery included?
(function ($) {
'use strict';
$( document ).ready(function() {
console.log( "ready!" );
});
})(jQuery);
/**
* @file
* Placeholder file for custom sub-theme behaviors.
*
*/
(function ($, Drupal) {
'use strict';
/**
* Example drupal behavior
*/
Drupal.behaviors.exampleBehavior = {
attach: function (context, settings) {
$('.example', context).once('example-behavior').each(function () {
});
}
};
})(jQuery, Drupal);
Drupal behaviors (Drupal.behaviors) are still part of javascript in core.
These behaviors will be executed on every request, including AJAX requests.
Can also be triggered from a module!
Including Drupal object
/**
* @file
* Placeholder file for custom sub-theme behaviors.
*
*/
(function ($, Drupal) {
})(jQuery, Drupal);
/**
* Example drupal behavior
*/
Drupal.behaviors.awesome = {
attach: function(context, settings) {
$('main', context).once('awesome').append('<p>Hello world</p>');
}
};
namespace:
A Drupal behavior has to have a unique namespace.
In this example, the namespace is awesome (Drupal.behaviors.awesome).
attach:
Contains the actual function that should be executed.
attach: function (context, settings) {
$('.example', context).once('example-behavior').each(function () {
alert('I\'m helping!');
});
}
once:
Using the .once('awesome') will make sure the code only runs once. Otherwise, the code will be executed on every AJAX request.
It adds a processed- class to the main tag (<main role="main" class="awesome-processed">) in order to accomplish this (core/jquery.once).
$('main', context).once('awesome').append('<p>Hello world</p>');
// Engine
Drupal.sbs_nu_v2.startAnimation = function() {
Drupal.sbs_nu_v2.animateActiveItem();
window.setInterval(function(){
Drupal.sbs_nu_v2.animateActiveItem();
}, 20000);
};
// Init
Drupal.behaviors.sbs_nu_v2 = {
attach: function (context, settings) {
$('#sbs-nu-v2', context).once('init-epg-box', function () {
Drupal.sbs_nu_v2.container = $(this);
Drupal.sbs_nu_v2.init();
});
}
};
Use Drupal.t()
// Make string available on translate interface
var close = Drupal.t('Close');
// Same, but with @placeholder
var nodesOfType = Drupal.t('Showing nodes of @type', {@type: nodeType});
Set breakpoint in devtools
misc/drupal.js
Use console.log();
console.log('Hi');
console.log(Drupal);
Tips:
Use jQuery's '.on' & '.slidetoggle'
Optionally: Create a new region 'mobile menu' and duplicate the menu blocks to that menu, hide with css & media querries
You can affect the output of certain HTML via preprocess functions.
For example, if you wanted to add a class to a menu and preferred to do this at the PHP level you can.
Add "<?php" opening-tag
Closing tag not needed
<?php
/**
* @file
* template.php
*/
// Add these when/if needed
use Drupal\Component\Utility\Html;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\media\Entity\Media;
// add functions below
Add class my-menu to all menus
/**
* Implements hook_preprocess_HOOK() for menu.html.twig.
*/
function mytheme_preprocess_menu(&$variables) {
// If there is not an existing class array, create an empty array.
if (!isset($variables['attributes']['class'])) {
$variables['attributes']['class'] = [];
}
// Merge with any classes that may have been set
// by other hook_preprocess_menu invocations
$variables['attributes']['class'] =
array_merge($variables['attributes']['class'], ['my-menu']);
}
Use var_dump(), dsm(), kpr(), kint() or XDebug
/**
* Implements hook_preprocess_HOOK() for menu.html.twig.
*/
function mytheme_preprocess_menu(&$variables) {
var_dump($variables);
var_dump($variables['main_menu']);
dsm($variables);
}
Use twig debug; see theme suggestions
https://www.drupal.org/docs/8/theming-drupal-8/modifying-attributes-in-a-theme-file
Not sure?
Consult google!
Add class my-main-menu to the main menu
/**
* Implements hook_preprocess_HOOK() for menu.html.twig.
*/
function mytheme_preprocess_menu(&$variables) {
if ($variables['menu_name'] == 'main') {
if (!isset($variables['attributes']['class'])) {
$variables['attributes']['class'] = [];
}
$variables['attributes']['class'] =
array_merge($variables['attributes']['class'], ['my-main-menu']); }
}
Add page link as a var: url
/**
* Implements hook_preprocess_node().
*/
function mytheme_preprocess_node(&$variables) {
if(isset($variables['content']['#node'])) {
$node = $variables['content']['#node'];
// Add node url
$variables['url'] = $node->toUrl();
}
}
in node.html.twig template:
<a href="{{ url }}" class="inner">
Add some classes to our paragraphs
/**
* Implements hook_preprocess_paragraph().
*/
function mytheme_preprocess_paragraph(&$variables) {
if ($entity = $variables['elements']['#paragraph']) {
// Title align.
if ($entity->hasField('field_title_align')) {
$field_title_align = $entity->get('field_title_align')->getValue();
$field_title_align = reset($field_title_align);
if ($field_title_align) {
$variables['attributes']['class'][] = 'title-align--'
. Html::cleanCssIdentifier(reset($field_title_align));
}
}
}
}
in paragraph.html.twig template:
<section{{ attributes.addClass(classes) }}>
<div class="container">
<div class="layout paragraph__layout">
In Drupal 8, themes can modify the entire theme settings form by adding a PHP function to either the themename.theme file or to a themename-settings.php file.
function foo_form_system_theme_settings_alter(
&$form,
\Drupal\Core\Form\FormStateInterface &$form_state,
$form_id = NULL
) {
// Work-around for a core bug affecting admin themes. See issue #943212.
if (isset($form_id)) {
return;
}
$form['foo_example'] = array(
'#type' => 'textfield',
'#title' => t('Widget'),
'#default_value' => theme_get_setting('foo_example'),
'#description' => t("Place this text in the widget spot on your site."),
);
}
Add textfield with title Widget
in themename.theme
foo_example: blue bikeshed
Add config/install/THEME.settings.yml file
Set default value
$foo_example = theme_get_setting('foo_example');
Retrieve value in php
<?php
function foo_preprocess_node(&$variables) {
$variables['foo_example'] = theme_get_setting('foo_example');
}
Make value available for twig files:
In themename.theme file:
{{ foo_example }}
In twig file: