Before we start diving into embedding SVG icons using Twig macros, let's discuss what a Twig macro is. If you read Twig's documentation, Macros are defined as follows:

Macros are comparable with functions in regular programming languages. They are useful to put often used HTML idioms into reusable elements to not repeat yourself.

Here is a small example of a macro that renders a form element:

{% macro input(name, value, type, size) %}
  <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
{% endmacro %}

Macros differ from native PHP functions in a few ways:

  • Default argument values are defined by using the default filter in the macro body;
  • Arguments of a macro are always optional.
  • If extra positional arguments are passed to a macro, they end up in the special varargs variable as a list of values.
     

As you can see, Macros allows us to simplify specific tasks and avoid repeating ourselves.  If you know Sass, I think of twig macros as Sass mixins. They help you write a specific functionality or behavior once and you can reuse it over and over without rewriting the code.

What we are building today

Today we will go over how to write a macro to embed svg icons in our site. This is handy because you can programmatically embed svg icons using twig logic.
Here's an example of what we are building today.  We want to cycle through a list of links and embed an svg icon to each link based on the link name/type (i.e. Twitter, Facebook, Instagram, etc.).

Social Media Icons

Social Media Icon Graphic

Let's start by writing the macro

We will create the icons macro in its own folder so it can be reused for embedding other icons in your site.

  1. In your project's theme directory, create a new folder called icons (i.e. src/components/icons)
  2. In the icons folder, create a new file: icons.twig
  3. Inside icons.twig, copy the snippet below
     
{% macro get(icon, iconClass) %}
  {% include './assets/' ~ icon ~ '.svg.twig' with {
    'icon': icon,
    'iconClass': iconClass
    } only
  %}
{% endmacro %}
  • The macro above is pretty straightforward.  We are passing two arguments (icon & iconClass).  This will allow us to specify a name for the icon and optionally provide the icon a css class.
  • Next, we are using a twig include statement to pass the svg file to the macro. Within the include statement, we are passing property | value pair for name and iconClass.
  • The svg icon will be embedded in a twig template and the icons file will follow the naming convention of xxxx.svg.twig (where xxxx is the name of your icon (i.e. facebook.svg.twig).  See next steps for more details.
     

Let's generate data for the icons

We are taking the component based theming approach for the icons. We are going to create a JSON object to pass to the twig template.

  1. Inside the icons folder, create a file called social-icons.json
  2. Inside social-icons.json paste the snippet below
{
  "items":
    {
      "icon": "facebook",
      "url": "https://facebook.com"
    }
  ]
}

What we've done here is write a JSON object which holds an array of items (for simplicity, we are only showing one item.  We'll add more later).  Each item has two properties, icon and url. We are naming this json file social-icons so that when we start using our icons macro we can do it in a twig template called social-icons.twig. More on this later.

 

Hosting the SVG code

We are going to create a twig file so we can embed the svg code in it.  This may seem strange but trust me, it works.

  1. Inside the icons folder, create a new folder called assets.  All future svg icons will be saved here
  2. Inside assets create a new file and name it facebook.svg.twig
  3. In it, paste the svg code below and save the file.
<svg aria-hidden="true" role="img" class="{{ iconClass|default('') }}" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><title>facebook</title><path d="M58 0H6C2.7 0 0 2.7 0 6v52c0 3.3 2.7 6 6 6h26V36h-8v-8h8v-4c0-6.613 5.388-12 12-12h8v8h-8c-2.2 0-4 1.8-4 4v4h12l-2 8H40v28h18c3.3 0 6-2.7 6-6V6c0-3.3-2.7-6-6-6z"/></svg>

The facebook svg icon above has been cleaned up and we've added a few things to it to make it more accessible and flexible.  For example, we've added aria-hidden & role attributes and we are also using a css class placeholder which will become handy for passing a unique css class to each icon.  

You may be wondering, why are you putting svg code in a twig template?  Good question.  We are doing this so we can take advantage of Twig's include statements for passing the icons.  We can nest twig templates inside other templates in order to render whatever is in the template.  In this case, an svg icon.

Let's put our macro to use

  1. Inside the icons folder create a new file called social-icons.twig
  2. Paste the code snippet below into it
{% import "icons.twig" as icons %}

{% if items %}
  <ul class="social-icons">
  {% for item in items %}
    <li class="social-icons__item">
      <a href="{{ item.url }}" class="social-icons__link">
        {{ icons.get(item.icon|lower, 'icon icon-' ~ item.icon|lower) }}
        <span class="social-icons__name">{{ item.icon }}</span>
      </a>
    </li>
  {% endfor %}
  </ul>
{% endif %}

Let's go over the code above

  • By naming the twig template to match the name of the json file (social-icons.twig), Twig automatically knows to look in the json file with the same name as the twig template for any data we are passing to twig.
  • We are importing the icons macro and creating an object out of it called icons.  
  • We are wrapping the icon in an *Unordered List* (UL).
  • Then we loop through the items array.
  • For each item in the array, we create a **List Item** (li) and assign a class of `social-icons--item`.
  • Next, we print an anchor and pass the icon's url and a class of social-icons__link.
  • Then we call the .get() function of the icons macro and pass the icon name from the JSON object (item.icon).
  • (Optional) The second argument is a class.  We concatenate the icon name so the class is unique on each icon (i.e. icon-facebook, icon-twitter, etc.)
  • Finally, inside a `<span>` we print the icon name for accessibility purposes.

 

Styling the icons

The styles below are totally optional but will help you at least get started with some basic styles.

.social-icons {
  background: $color-alabaster;
  border: 1px solid $color-alto;
  display: flex;
  justify-content: center;
  list-style: none;
  margin: 0;
  padding: 20px;

  .icon { 
    fill: $color-tundora;
    transition: fill 0.3s ease-in-out;
 
    &:hover, 
    &:focus { 
      fill: $color-orange; 
    } 
  } 
} 

.social-icons__name {
  display: block; 
  text-indent: -999999px; 
}

.social-icons__item {
  margin: 0 20px;
}

Adding more icons

  • To add more icons to the component, edit the social-icons.json file and replace its content with the snippet below. 
  • Next, create each of the corresponding icon files inside asset as we did above for the facebook icon.  For example, inside assets create a new file called twitter.svg.twig and paste the svg code for the twig icon.  Do this for each icon you wish to add to the collection.
  • Finally, clear your cache.
{
  "items":[
    {
      "icon": "facebook",
      "url": "https://facebook.com"
    },
    {
      "icon": "twitter",
      "url": "https://twitter.com"
    },
    {
      "icon": "youtube",
      "url": "https://youtube.com"
    },
    {
      "icon": "github",
      "url": "https://github.com"
    },
    {
      "icon": "instagram",
      "url": "https://instagram.com"
    }
  ]
}

How to test your icons

Given that we are using the component-based approach to building the icons, the only way you can test them is if you are working on a project where you are using the same approach and have a styleguide in place.  For this post, I created a new Drupal 8 theme using Mediacurrent's Theme Generator and built the icons system in it.

Get the demo theme

You can clone or download the demo theme I created and follow the instructions on the repo to test the icons.  Get the demo theme here.

Resources: