Notes from the Team

Custom Theme Creation in Magento 2

Magento

Magento Logo

Welcome to part 3 of our Magento 2 tutorial series. We have now covered environment setup, Magento installation and general Magento concepts. We will now dive into developing a custom theme for Magento.

Themes are what define the appearance and design of your Magento stores. The default Magneto theme is a ‘Blank’ style. Since we installed the sample data, our theme is currently set to ‘Luma’. To see the difference, we will be disabling Luma and switching to the Blank style.

Changing Active Theme

First, login to your admin HTML and click Content->Configuration.

Store Configuration Screenshot

We want to edit the theme in this last selection. There are multiple options here due to the ability to define a global default theme, a theme for a specific website, and even more specifically themes for stores on that website. These are more advanced topics which we will not yet cover. However if you click the edit button for this store it will bring you to a configuration page. Here we want to change the Applied Theme from the Luma theme to the Blank theme.

Theme Selection Screenshot

After we do this, save the configuration. Once it has finished saving, take a look at our store.

Blank Theme Screenshot

As you can see, the store is now simpler. This is the default blank theme which can be used to build your own themes, or you can base your theme on Luma. Note that our categories and products are still in our store, only the appearance has changed. Now that we know how to switch which theme is active, we’re going to build our own theme!

Theme Setup

The first thing to know is that your own themes will be located in app/design/frontend or app/design/adminhtml depending on what you are editing. 1. Create a module folder in app/design/frontend. We will be calling ours ‘Training’ 2. Inside of the Training folder, create ‘TestTheme’ a folder which will serve as the name of your theme. 3. Create a theme.xml inside of TestTheme

app/design/frontend/TestTheme/theme.xml

<theme xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.xsd">
   <title>Test Theme</title>
   <parent>Magento/luma</parent>
</theme>

Breaking this XML code down: * The title attribute is how the name of the theme will be displaying in the admin CMS * The parent is the fallback for this theme, if any file doesn’t exist in our theme it will ‘look up’ to the parent to find the same file and use that instead.

We then need to create a composer.json file in TestTheme

app/design/frontend/Training/TestTheme/composer.json

{
 "name": "magento/training-test-theme",
 "description": "N/A",
 "require": {
   "php": "~5.5.0|~5.6.0|~7.0.0",
   "magento/theme-frontend-luma": "~100.0",
   "magento/framework": "~100.0"
 },
 "type": "magento2-theme",
 "version": "1.0.0",
 "license": [
   "OSL-3.0",
   "AFL-3.0"
 ],
 "autoload": {
   "files": [ "registration.php" ]
 }
}

This file will serve to be the way which your theme is distributed to other users if you choose to do so, you can read more about Composer integration with Magento here Next we will create the registration.php file which is referenced in the composer.json file

app/design/frontend/Training/TestTheme/registration.php

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

This file will tell Magento where to find the theme when it attempts to load it

We should now be ready to try to enable this new theme! Follow the same steps as before to switch from Luma to Default, except now you should see our new theme as an option. After saving the configuration, if we visit our store we should see this:

Blank Luma Theme

Now our theme is an exact copy of the Luma theme, since we have not overwritten anything. But wait, where did all of the front page go? Luma actually uses static blocks to render some information, so we will setup the front page to display this as well. Static blocks can be very useful if you have content that you wish to modify frequently or if the client desires an easy way to change some portion of content on a page without having to edit the source code.

Inserting Static Block from the CMS

Login to the admin panel, and go to Content->Pages, click Edit on the Home Page row

Under content, inside of the editor add

and save. The {{}} is an escape character sequence used by Magento to indicate that the contents of the curly braces should be processed by Magento, usually into a block. You can find the block which we are referring to here by going to Content->Blocks and finding the block which matches the identifier we used for block_id If you visit the front page you should now see the nice landing page that was there previously.

Modifying Existing Templates

Next we want to add a message about a money back guarantee to all products in their description. Since we don’t want to have to include this in every product description, we can modify the template to display this message for us! Navigate to any product in our store.

Product Details Screenshot

This highlighted area is what we are targeting, we want to add a message to the end of this description area. But how can we determine where this is HTML is coming from? We can use template path hints.

Go to your admin panel, navigate to Stores->Configuration. On the left side of this page under Advanced, click Developer. Under Debug turn Enable Template Path Hints for Storefront to Yes.

Once again, for these changes to take effect we need to clear the cache.

Php bin/magento cache:flush

You may quickly be noticing that if you do something and what you expected to happen doesn’t, then clear the cache. Caching can be disabled in the admin panel if you do not wish to flush the cache so frequently but this will result in very slow page loads.

Now if you refresh the product page you’ll see a whole lot of information. While this may look overwhelming, let’s take a look at what’s going on. Each red dotted line surrounding a piece of the page is associated with a file path. This file path is the template file from which this HTML is being created. If we look at the description area we can see that there is a path like

/vendor/magento/module-catalog/view/frontend/templates/product/view/attribute.phtml

However if you look on the page this attribute.phtml file is being used in multiple places, so we can’t just modify it directly without changing many other things. Sometimes the template hints are not lined up correctly with what they are referring to, as is the case with the template file we are looking for. Above the product area you can see a path hint for

/vendor/magento/module-catalog/view/frontend/templates/product/view/details.phtml

which just from the name seems to be what we’re looking for. Navigate to that folder, copy the contents of the entire file. Let’s create an override for this file by copying the file structure. Inside of our TestTheme, create a Magento_Catalog folder This name comes from the path we saw earlier _

/vendor/magento/module-catalog/view/frontend/templates/product/view/details.phtml

Inside of Magento_Catalog create a file structure as follows:

TestTheme/Magento_Catalog/templates/product/view/details.phtml

Inside of the new details.phtml file, paste what you copied from the other details.phtml file. You can see this file is looping over several detailedInfoGroup items as name, so we can check in the loop for the name we want which is product.info.description. If you are unsure what you want to target, you can always do <? php echo $name ?> somewhere in the loop so you can determine what each section is called by displaying the name on the page.

TestTheme/Magento_Catalog/templates/product/view/details.phtml

<?php if ($detailedInfoGroup = $block->getGroupChildNames('detailed_info', 'getChildHtml')):?>
   <div class="product info detailed">
       <?php $layout = $block->getLayout(); ?>
       <div class="product data items" data-mage-init='{"tabs":{"openedState":"active"}}'>
           <?php foreach ($detailedInfoGroup as $name):?>
               <?php echo $name ?>
               <?php
               $html = $layout->renderElement($name);
               if (!trim($html)) {
                   continue;
               }
               $alias = $layout->getElementAlias($name);
               $label = $block->getChildData($alias, 'title');
               ?>
               <div class="data item title"
                    aria-labeledby="tab-label-<?= /* @escapeNotVerified */ $alias ?>-title"
                    data-role="collapsible" id="tab-label-<?= /* @escapeNotVerified */ $alias ?>">
                   <a class="data switch"
                      tabindex="-1"
                      data-toggle="switch"
                      href="#<?= /* @escapeNotVerified */ $alias ?>"
                      id="tab-label-<?= /* @escapeNotVerified */ $alias ?>-title">
                       <?= /* @escapeNotVerified */ $label ?>
                   </a>
               </div>
               <div class="data item content" id="<?= /* @escapeNotVerified */ $alias ?>" data-role="content">
                   <?= /* @escapeNotVerified */ $html ?>
                   <!-- Display Money Back Guarantee on All Products -->
                   <?php if ($name === 'product.info.description'): ?>
                       <?php echo 'Buy with confidence! 30 day money back guarantee if you are not happy with your purchase!' ?>
                   <?php endif; ?>
               </div>

           <?php endforeach;?>
       </div>
   </div>
<?php endif; ?>

After we have added this code, you should be able to clear your cache and then refresh any product page to see this new message being displayed at the bottom of every description.

So while this works just fine for what we were trying to accomplish, what if we wanted to add another tab to the product description instead? In addition to Details, More Information, and Reviews we want to have a ‘Warranty Info’ tab instead of the text at the bottom of the description in order to bring more attention to the warranty. Let’s do that next!

Layouts and Custom Template

We have been working with templates, now we are going to modify a layout. We are going to find the template we want to target for modification. Since we are going to change the location of the details block we have already modified, we can look in

vendor/magento/module-catalog/view/frontend/layout/

and find the layout file we wish to modify. The one we are looking for is catalog_product_view.xml because it refers to the product.info.details block. We want to match the file name in order to override the layout with anything we put in our file. The more you work with Magneto, the more easily you will be able to determine where things are most likely to be located in the folder structure. Create the following file in our custom theme.

TestTheme/Magento_Catalog/layout/catalog_product_view.xml

<?xml version="1.0"?>
<page layout="1column" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
   <body>
       <referenceBlock name="product.info.details">
           <block class="Magento\Catalog\Block\Product\View" name="warrantyinfo.tab" as="warrantyinfo" template="product/view/warranty_info.phtml" group="detailed_info" >
               <arguments>
                   <argument translate="true" name="title" xsi:type="string">Warranty Info</argument>
               </arguments>
           </block>
       </referenceBlock>
   </body>
</page>

In this layout file, the reference block is product.info.details. The referenceBlock is the block we are targeting for modification. We had previously modified the details template, now we are changing the layout. We are specifying a template we want to use for the content of this new tab with the template="product/view/warranty_info.phtml", which we have yet to create. Lets do that now.For the sake of keeping it simple, we’re just going to put an unordered list with a couple of list items in our template. TestTheme/Magento_Catalog/templates/product/view/warranty_info.phtml

<ul>
   <li>30 Day Money Back Guarantee</li>
   <li>Highest quality materials, buy with confidence!</li>
</ul>

Now clear your cache and reload the page, you should see a new tab added to the product details area. If you want you can now delete the details.phtml in the templates/product/view folder since we have the same information in a new tab now. Now that we’ve added a tab, how do we remove one? Let’s get rid of the reviews tab next.

Removing a tab is very similar to adding a tab. Inside of out catalog_product_view.xml we just need to add one line

<referenceBlock name="reviews.tab" remove="true" />

As you can see we once again referenced a block by name, and set remove to true. You may be wondering how we determine what the name of the block we want to remove is. The review block name is set in the Review module. If we look at vendor/Magento/module-review/view/frontend/layout/catalog_product_view.xml you’ll see this piece of code

vendor/Magento/module-review/view/frontend/layout/catalog_product_view.xml

<referenceBlock name="product.info.details">
   <block class="Magento\Review\Block\Product\Review" name="reviews.tab" as="reviews" template="Magento_Review::review.phtml" group="detailed_info">
       <block class="Magento\Review\Block\Form" name="product.review.form" as="review_form">
           <container name="product.review.form.fields.before" as="form_fields_before" label="Review Form Fields Before"/>
       </block>
   </block>
</referenceBlock>

This all looks very similar to what we have already done to add our own custom tab, except this is adding a Review tab named reviews.tab. So if we target this for removal in our themes catalog_product_view.xml then it is no longer displayed. Clearing the cache and refreshing the page will result in the reviews tab no longer being on the product page.

So far we have added and removed a block, now let’s move a block. This follows a very similar paradigm as before.

<move element="product.info.details" destination="product.info.main" before="product.info.price"/>

Instead of referenceBlock we are using move. The ‘referenceBlock’ here is essentially element, which is the block which we want to move. Destination is the container we want the block moved to, and before allows to have further control over where the block will be located. You can also use after. Destination and before were determined by looking in the catalog_product_view.xml inside of module-catalog.

With the above code in our custom layout, you’ll see our product details block move to above the product price on the right hand side of the page. While this example may not be particularly useful, it does show how simple and powerful moving blocks can be.

In this article we have covered how to change our active theme, create a custom theme, and override a template file with our own. We also covered adding, moving, and removing blocks from a layout, and creating a custom template for new content. These are the fundamentals of custom theme creation in Magento. In the next article we will be covering creating a custom module in order to add custom blocks and controllers to our store.

About The Author

David Friend
David Friend

Web Developer

The Next Step

Connect

[email protected] (804) 272-1200