Skip to content

Magento 2 Theme Setup

Prerequisites and Assumptions

Relevant Technologies

Relevant Magento 2 Concepts

Creating Themes

When starting a new theme it is important to decide what your starting point should be. There are four options for extending themes:


Unless you intend to make massive changes you should always extend from the Luma theme. The Luma theme has a much better starting user experience over the Blank theme.

It is also important to note that Magento's automated acceptance tests are written with the Luma theme in mind. The tests are less likely to pass for themes that do not extend Luma meaning that the tests will need overriding to support them.


Extending from Blank is useful when you intend to create a completely new theme which is very different from Luma. Themes that completely change the layout or set out to make drastic changes should use Blank as a starting point.


This is the new PWA studio driven theme provided by Magento. At the time of writing it is still not marked as ready for production use. It is not advisable to use this theme unless you know what you are doing and are at least adept with Javascript and newer frontend technologies.

Third party themes

There are many third party themes available for Magento 2, many are rebuilt versions from Magento 1. Not all themes are made equal and you should be cautious when selecting a third party theme. Be sure to test the theme thoroughly in development mode so that any errors with the theme files are caught as early as possible.

Again, as with Blank, unless the theme developer has written test overrides the tests will need updating and will likely need a considerable amount of work to get passing.

New Theming 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 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


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

- app/design/frontend/(Vendor_Name)/(ThemeName)
    - (Module_Name)/ sets the theme's templates and layout for each module, such as Magento_Catalog
    - Magento_Theme/ for module-agnostic templates and layout
        - layout/ for layout XML files
        - template/ for PHTML template files
    - media/ contains a preview file
    - web/ contains static content for delivering to the browser
        - css/
            - source/ - less files
        - images/
        - js/
    - registration.php registers the theme
    - theme.xml contains data about the theme such as the name and parent theme





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

<theme xmlns:xsi="" xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.xsd">
    <title>Edmonds Commerce Theme Tutorial</title>

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


  • A module's template files are contained in its view/(area)/templates folder
  • A theme's template files are contained in its Magento_Theme/templates folder

PHTML Templates 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 ?>
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. This meant that the template had access to the block class's protected and private methods using $this

Rendering child blocks

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

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

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


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:

- Magento_Theme/
    - layout/
        - catalog_product_view.xml
        - cms_index_index.xml
        - default.xml  //applies to all pages

XML Instructions

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

  • <head>
  • <body>
  • <html>
  • <update>

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


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

    <title>Page Title</title>
css, script, link

Adds CSS and Javascript resources to the page. The <link> tag allows for both IE-conditional comments, and to defer loading 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 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="" htmlTag="div" htmlClass="class" after="-">
    <!-- Blocks and other containers injected here -->


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> instruction allows for a block to be moved from one block or container to another.

<move element="" as="" destination="" before="-" />


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

<referenceContainer name="footer">
<!-- Inject more content in to the container -->

<referenceBlock name="header">
<!-- Inject more content in to the block -->

Block Types

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

  • Magento\Framework\View\Element\Text is used for displaying simple text on the page. Good for debugging your layout XML
  • Magento\Framework\View\Element\Text\ListText is extended from Text, this is used to contain a sorted list of other blocks. Blocks added as children to this are automatically rendered
  • Magento\Framework\View\Element\Messages are their own block type in Magento 2. These represent the "success", "error" etc banners
  • Magento\Framework\View\Element\RedirectRedirect perform a client-side redirect, if ever that's desirable
  • Template for loading a block with an included template. The template is set with the template attribute, e.g: template="Module_Name::path/to/template.phtml"

Block methods

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

To set constructor parameter values, arguments can be added:

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

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>

Static content (CSS/Images/JS)

These files are stored in the theme's web sub-folder, under their own css, js and images sub-folders.

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.


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
    • grunt clean clears the symlinks
    • grunt refresh clears then regenerates the symlinks, equivilant to clean then exec
    • Some commands allow you to specify the theme e.g. grunt less:theme
  2. Using bin/magento setup:static-content:deploy
    • Used in production
    • Converts the LESS files to CSS for the theme
    • Handles theme fallback and locales


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

Further Reading


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
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
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

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:

            'tabs'], function ($) {
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 sub-folder 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:

- app/design/frontend/(namespace)/(theme)/
    - Magento_Catalog/
        - layout/
            - catalog_product_view.xml
        - templates/
            - product/
                - list.phtml
                - view.phtml

Common Snippets


URL Generation

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


New container

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

New blank template

<block class="Magento\Framework\View\Element\Template"
       name="block_name" />

Removing sidebar items

<referenceBlock name="" remove="true"/>
<referenceBlock name="" remove="true" />
<referenceBlock name="" 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

  • Logo image and dimensions (now includes SVG)
  • Title prefix and suffix as displayed in the tab/title bar
  • Meta keywords/description as the default if the page doesn't specify its own
  • Favicon image
  • Welcome Text which is displayed for logged out users
  • Miscellaneous HTML, scripts and CSS inserted before the closing </body> and </html> tags

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


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 en_GB

Further reading