Getting started with your own Magento 2 Theme

Prerequisites and Assumptions

Relevant Technologies

Relevant Magento 2 Concepts

On this page

New Themeing Concepts in Magento 2

Module-specific theme files

In Magento 2 there is no longer a “base” package or “default” theme. Now, modules contain their own files within their view/(area)/(layout|template|web) folders.

Here are a few examples:

Themes

Themes are a new standalone concept in Magento 2. These themes exist to create their own template, layout, CSS files as a self contained package.

Themes can also specify their own overrides to modules’ theme files, rather than relying purely on file path matching.

Static content

Static resources such as CSS, images and JS files are no longer served from the theme folder itself. Instead they’re published to the pub folder using either symlinks or copies.

Creating the theme structure

Folders

The first step is to create a new namespaced folder path in app/design/frontend/. This would look like app/design/frontend/(Vendor_Name)/(ThemeName)

Magento 2 theme skeleton folders

Configuration

registration.php

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::THEME,
    'frontend/EdmondsCommerce/ThemeTutorial',
    __DIR__
);

theme.xml

This file specifies information about the theme, as used in Magento’s admin

<theme xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.xsd">
    <title>Edmonds Commerce Theme Tutorial</title>
    <parent>Magento/luma</parent>
    <media>
        <preview_image>media/preview.jpg</preview_image>
    </media>
</theme>

Applying your theme

To apply a theme to your store, navigate to the Magento Admin’s Design Configuration page:

  1. Log into the Magento Admin
  2. Click Content, and then under Design click Configuration
  3. Choose your website/store/store view level, and click Edit
  4. Set your Applied Theme

Theme Files

Templates

Templates are the way to generate frontend HTML content with dynamically generated content.

Magento allows for the ability to add new template engines to take advantage of other templating languages (such as twig) but only comes with PHP and XHTML renderers out of the box.

Bonus fact: the Template Hints are rendered using one of these Template Renderers in Magento\Developer\Model\TemplateEngine\Decorator\DebugHints

Folders

PHTML Overview

Standard Magento templates are written as PHTML files. This involves a mixture of PHP and HTML, all with a focus on presenting the view-level content. This means business logic should be contained in the Block class and called using its public methods (more on accessing block methods below)

PHTML files look like

<a href="<?php echo $url ?>">
    <?php echo $text ?>
</a>

PHTML files use PHP’s alternative syntax for if, foreach, while etc control structures:

<?php foreach($array as $item): ?>
    <span><?php echo $item ?>
<?php endforeach; ?>

<?php if($test == "test"): ?>
    <span><?php echo $test ?>
<?php endif; ?>

Accessing block methods

As part of the rendering process, Magento makes available a $block variable which represents an object instatiation of the Block class. As with any other PHP object, you can call its public methods from within the template.

This is a change from Magento 1, where the template was included within the class, and therefore had access to its protected and private methods.

Rendering child blocks

Inserting child blocks is pretty much the same as Magento 1:

<?php echo $block->getChildHtml('block.name'); ?>

Translations and Escaping

Translations can be implemented using the __() method. Note that this is not a method call on an object:

<span><?php echo __('Learn More') ?></span>

Magento\Framework\View\ElementAbstractBlock provides public stripTags() and escapeHtml() methods. These strip a string of HTML entities and tags respectively.

<h2><?php echo $block->stripTags($_product->getName(), null, true); ?></h2> <!-- the parameters null and true are for string $allowableTags and bool $allowHtmlEntities -->
<span title="<?php echo $block->escapeHtml(__('More Info')); ?>"</span>

Further Reading

Layout

In Magento 2, layout XML works a little differently. The most obvious difference is that each layout handle is its own XML file, so your theme’s layout folder might look like this:

XML Instructions

Layout XML in Magento 2 seperates its instructions into four groups:

All but the last one correspond to the HTML elements that contain them. <update> is simply a way to include another handle’s instructions into the current file.

Magento 2 has a few instructions in Layout XML. Some will be familiar from Magento 1, others are new.

Head Layout Instructions

Title

Sets the page’s title, as used in the window title bar and tab

<title>Page Title</title>

Adds CSS and Javascript resources to the page. The <link> tag allows for both IE-conditional comments, and to defer loadinf the script until the page has loaded

<css src="Namespace_Module::css/style.css" />

<script src="Namespace_Module::js/script.js" />

<link src="Namespace_Module::js/script.css" ie_condition="IE 9" defer="defer" />
Meta

Normal meta tags for the page, with key/value pairs

<meta name="content-type" content="text/html; charset=utf-8" />

Body Layout Instructions

Full documentation on body layout instructions

Containers

Containers are a new concept in Magento 2. Containers are intended to represent a part of a page, rather than a block of content. They’re not backed by a PHP Class, and can only create an HTML element.

Attributes can be assigned to the containers, and they can be sorted within their parents.

Examples of standard containers are the header, footer, left, right and content areas.

<container name="container.name" htmlTag="div" htmlClass="class" after="-"></container>
Blocks

Blocks are used to add content to the page, and are backed by a PHP Block class. More on Blocks

<block class="Namespaced\Path\To\Block\Class" name="top.container.welcome"></block>
Move

The <move> instruction allows for a block to be moved from one block or container to another.

<move element="old.name" as="new.name" destination="destination.block.name" before="-" />
ReferenceContainer/ReferenceBlock

This allows for more content to be added into a block or container

<referenceContainer name="footer"></referenceContainer>

<referenceBlock name="header"></referenceBlock>

More on Blocks

Block Types

There are many types of predefined Block in Magento 2, all contained within Magento\Framework\View\Element namespace.

Block methods

Parameters can be called on Blocks’ constructors, as well as their class methods.

To set constructor parameter values, arguments can be added thusly:

<arguments>
    <argument name="logo_img_width" xsi:type="number">220</argument>
    <argument name="logo_img_height" xsi:type="number">70</argument>
</arguments>

And class methods by specifying an action:

<action method="methodName">
    <argument name="methodArgumentName" xsi:type="text">value</argument>
    <argument name="methodArgumentArray" xsi:type="array">
        <item name="array_key" xsi:type="number">1337<item>
    </argument>
</action>

Static content (CSS/Images/JS)

These files are stored in the theme’s web subfolder, under their own css, js and images subfolders.

In Developer mode these files are read through symlinks created inside the pub folder. In production though they’re published to the pub folder using the bin/magento setup:static-content:deploy command.

Images and CSS

A theme’s images and CSS files are stored in the web/images and web/css folders respectively.

LESS

Magento 2 natively supports using CSS preprocessors, and uses Less CSS for its own Luna theme.

Storing Less files

Less files are contained within a modules web/css/source/ folder, with a naming convention that base files are named as file.less and files to be imported are named with an underscore prefix as _file.less.

Importing other less files

Because normal Less @import directives use paths relative to the include path, they’re not aware of Magento’s fallback system. For this reason, other .less files should be included with //@magento_import file.less - yes, with the // comment. This means the Less compiler will ignore it, allowing Magento to handle the fallback in its round of compilation.

Referencing images

Image URLs are relative to the web/css folder, so image paths should be url('../images/path/to/image.jpg')

Compiling your Less

There are two ways to compile your less files:

  1. Using grunt commands:
    • grunt exec sets up symlinks from the files in pub/static to your module/theme’s files
    • grunt less compiles your less to CSS, and then sets up the symlinks to those
    • grunt watch runs a file watcher to track changes to the less files, and compiles the CSS on the fly
  2. Using bin/magento setup:static-content:deploy
    • Usually used in production
    • Copies the files themselves into the pub folder

Variables

Magento provides a set of helpful variables for use in the Less files:

Further Reading

JavaScript

Magento recommends including Javascript as part of templates rather than through layout XML to ensure they run as part of the body.

It makes use of RequireJS to pull in dependencies. These are run with the script tag with a Magento-specific type:

<script type="text/x-magento-init"></script>

Pulling in a Javascript assets

To pull in the module Magento_Configurable’s js/configurable.js file (which will exist in the pub folder):

<script type="text/x-magento-init">
   require(["Magento_ConfigurableProduct/js/configurable"], function(Configurable){
        // your function body here
   }); 
</script>

To pull in a JavaScript file from your theme, use a relative file path from your theme’s web folder:

<script type="text/x-magento-init">
   require(["js/customFile.js"], function(){
        // your function body here
   }); 
</script>

Javascript libraries provided by Magento in the lib folder are accessed by name:

<script type="text/x-magento-init">
   require(["jquery"], function($){
        // your function body here
   }); 
</script>

jQuery Widgets

Not to be confused with UI Components, these extend from jQuery UI components and are useful widgets to use on the frontend. They include accordions, calendars, menus and tabs.

They can be initialised similar to the following in your template file:

<script>
    require([
        'jquery',
        'tabs'], function ($) {
        $("#footer-accordion").accordion();
    });
</script>

A full list of Widgets can be found at the jQuery Widgets DevDocs page.

Further Reading

Overriding modules’ view files

In Magento 1, overriding other modules’ assets was as simple as matching the file path in your own theme. In Magento 2, things are pretty similar, except you specify which module you want to override.

  1. In your theme (app/design/frontend/(namespace)/(theme)), create a folder matching the NameSpace_Module you’re overriding
  2. Create a subfolder for the type of file you’re overriding: templates, layout or web
  3. Match the original module’s file path, and add your content there

An example would look like:

Common Snippets

Templates

URL Generation

<a href="<?php echo $block->getUrl('path/to/page') ?>">Link</a>

Layout

New container

<container name="new_container" htmlClass="container_css_class" htmlTag="div">
    <!-- blocks or containers here -->
</container>

New blank template

<block class="Magento\Framework\View\Element\Template"
       template="Magento_Theme::path/to/template.phtml"
       name="block_name" />

Removing sidebar items

<referenceBlock name="catalog.compare.sidebar" remove="true"/>
<referenceBlock name="view.addto.compare" remove="true" />
<referenceBlock name="category.product.addto.compare" remove="true" />
<referenceBlock name="customer-account-navigation-wish-list-link" remove="true"/>
<referenceBlock name="customer-account-navigation-billing-agreements-link" remove="true"/>
<referenceBlock name="customer-account-navigation-downloadable-products-link" remove="true"/>
<referenceBlock name="customer-account-navigation-newsletter-subscriptions-link" remove="true"/>
<referenceBlock name="customer-account-navigation-my-credit-cards-link" remove="true"/>

Admin Configuration

A few aspects of the theme can be user-configured through the Admin. This is found in Content > Design > Configuration > (store) > Edit

Image placeholders are configurable at Stores > Configuration > Catalog > Product Image Placeholders

Deployment

When deploying to the server, you should change from Developer mode to Production mode.

With this change in place, resources are served directly from the pub folder rather than through symlinks or the static.php file. To this end you need to ensure your resources are published.

This is accomplished using the bin/magento setup:static-content:deploy command. This will loop through the themes and modules to deploy their static files to the pub folder.

Remember to set a locale if an Admin user uses a non-default locale, or else they’ll have no CSS in their Admin: bin/magento setup:static-content:deploy --language en_GB

Further reading


Tags: magento2themefrontend