Rainbow Stones Hero Image

SVG Sprites and Twig Macros in Craft CMS

Using gulp tasks to combine svg files into a single minified file and then using macros in Craft cms easily output icons with a single line of code in your templates.

What is a Macro?

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.

Twig Documentation

In the official twig docs Macros are described as 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..

Macros are similar to Includes but are used differently.  An include is a single file that contains your whole partial. You can pass variables to an include using the with parameter. In the include partial you need to add conditionals around the included variables in case they are not passed through. With a macro all passed variables are optional and will return nothing if not passed in the macro tag. Additionally a macro file can contain multiple macros that you call individually using the name of each macro.

What is the difference between a macro and an include? This is an excellent question with an answer linked which in part states:

  • Macros: reusable markup across a lot of templates
  • Includes: part of "pages" that are extracted for readability and reusability

The reason for this, as far as I understand, is that you can only use the include tag to render the complete file containing your partial. Whereas the macro tag allows to render a partial from a file with multiple macros / partials in it.

Carlcs on Craft Stack Exchange

Gulp

I'm working a new site that has almost two dozen svg icons being used across the site. Instead of having to load up each icon individually resulting in additional http requests I wanted to combine them into one file and call using the svg symbols.

The first step is to take all the svg icons and combine them into a single svg file that wraps the individual icons in a symbol tag. To do this I set up a gulp task. At this pointThis task requires gulp-svgmingulp-svgstore, and gulp-rename. In your terminal npm install packageName --save-dev for each of these.

// load your plugins and then create this task.

gulp.task("svg-sprite", function () {
  return gulp.src("./public/assets/images/icons/*.svg")
    .pipe($.svgmin())
    .pipe($.svgstore())
    .pipe($.rename('icons.svg'))
    .pipe(gulp.dest("./public/assets/images/"));
});

This bit finds all svg files in /public/assets/images/icons/, minifies, the combines and finally renames the resultant file to icons.svg before placing it in the /public/assets/images/ directory.

Macro Time

Now we have our svg sprite it's time to get it working in our templates. The best way to do this is with a macro. First create a macro template. I called mine _macros.html and placed it in the templates directory of your Craft CMS install. Here is the full macro.


{% macro icon(iconId, className, width, height) %}
    <svg class="icon {{ className }}" role="img" title="{{ iconId }}" width="{{ width|default('24') }}"  height="{{ height|default('24') }}">
        <use xlink:href="{{ url('/assets/images/icons.svg#' ~ iconId) }}"></use>
    </svg>
{% endmacro %}

Now in each template that you want to use the macro add the following code.

{% import '_macros' as macros %}

Back to the macro file, there are four parameters in use here:

  1. iconId
  2. className
  3. width
  4. height

Each of these gets passed to the macro when we call it in the template. The only parameter that is required for the svg to work is the iconId as that is used to determine which symbol is used in our sprite. The iconId is created in the gulp task above and is the name of the original svg file. so if your svg was called facebook.svg then you would pass in "facebook" to the iconId parameter.

It's also important to note that the height and width parameters have defaults set to 24. If you don't pass in a height/width then the svg will automatically be set to 24 for both. For SVGs I find that it's necessary to set a height/width inline otherwise with large svgs there's a large version flashed on the screen on page load. 

If you're parameters don't have a default and nothing is passed then they output nothing. This is different than with includes where any variables in the template must be passed or conditionals used to avoid errors.

In your template where you want your svg to appear you would place this code:

 {{ macros.icon("facebook", "icon-blue", "80", "80") }}

That code outputs the svg and using the facebook symbol with a class of icon-blue and a height/width  of 80.  The  height & width is easily overwritten with css.

The final rendered code is:

<svg class="icon icon-blue" role="img" title="facebook" width="80" height="80">
    <use xlink:href="/assets/images/icons.svg#facebook"></use>
</svg>

If you do not include the id of the icon, the macro will output nothing and the SVG will not know which symbol to include. If you do not include a height or width they default values of 24 will be used. For example, below the svg will output the facebook icon without additional classes and using the default values for height and width.

{{ macros.icon("facebook", "", "", "") }}

Conclusion

Macros are useful for bits of programmatic code that will be re-used across the site which will allow you to pass optional variables. Another example of this is the excellent lazyFocusImager macro which allows you to easily add responsive images that lazy load and use focuspoint to smartly crop/resize the image. I have added a modified version of this macro to my macros file.

Learn more about SVGS by reading: How to work with SVG icons.