Using macros to improve consistency in your Craft CMS templates

I find Twig macros great for enforcing consistency in the Craft CMS templates I work on. Here are a few examples of how I use them.


All these examples use macros I've created in a template called _macros/helpers and imported like so:

{% import '_macros/helpers' as helpers %}

You should read the Twig documentation on macros and importing to get a full explanation of this.

Consistent date formatting

When you're including dates in your templates you don't want to have to question the correct format each time. The format should be set once at the beginning of the build. If you decide part way through the project to change the format you should be able to update it once rather than needing to search and replace across all your templates.

{% macro date_formatted(givenDate) %}
    {{ givenDate | date('jS F Y') }}
{% endmacro %}

Using this macro is as simple as this:

{{ helpers.date_formatted(entry.postDate) }}

This would result in a date formatted like: 7th March 2016.

Different format types

You might have a couple of types of formatting used throughout your templates. Maybe you display dates in listings in a short format like '10/03/2016', but on detail pages you expand them. Your macro can handle that too:

{% macro date_formatted(givenDate, type = 'expanded') %}
    {% switch type %}
        {% case 'short' %}
            {{ givenDate | date('m/d/Y') }}
        {% case 'expanded' %}
            {{ givenDate | date('jS F Y') }}
    {% endswitch %}
{% endmacro %}

In this case you'd use the macro like this:

{{ helpers.date_formatted(entry.postDate, 'short') }}

You might also want a date format than contains superscript text. So far this crazy escaping, along with an additional raw filter, is the only method I've found works consistently.

{{ entry.postDate | date('j\\<\\s\\u\\p\\>S\\<\\/\\s\\u\\p\\> F Y') | raw }}

This would result in a date formatted like: 7th March 2016.

You definitely don't want to have to repeat that throughout your templates so containing it within a macro works perfectly.

{% macro date_formatted(givenDate) %}
    {{ givenDate | date('j\\<\\s\\u\\p\\>S\\<\\/\\s\\u\\p\\> F Y') | raw }}
{% endmacro %}

Reduce the repeated reference of paths

Twig gives us the include tag to include a template within another. Those templates can live anywhere within your /templates/ folder but if your project architecture dictates a particular location to store them, you want to:

  1. Try to enforce that location
  2. Make life as simple as possible for your template developers.

Typically my include templates are located at /templates/_main/includes/ and so include tags might look something like this:

{# header partial #}
{% include '_main/includes/layout/header' %}

{# footer partial #}
{% include '_main/includes/layout/footer' %}

To ensure consistency and to make life a little easier for the template developers I could use a macro instead:

{% macro include(filepath) %}
    {% include '_main/includes/' ~ filepath %}
{% endmacro %}

This can then be used in your template like so:

{{ helpers.include('layout/header') }}

Passing variables

You don't want to lose the ability to pass variables so your macro should handle that too:

{% macro include (filepath, variables = {}) %}
    {% include '_main/includes/' ~ filepath with variables only %}
{% endmacro %}

Then we use the macro like this:

{{ helpers.include('layout/header', {
    pageTitle: 'Contact us'
}) }}

Multiple include locations

In my project architecture I possibly have multiple locations for includes templates. For example:

  1. /templates/_main/includes/ (my main website includes)
  2. /templates/_emails/includes/ (my email template includes)

Again our macro could handle this:

{% macro include(filepath, context = 'main') %}
    {% switch context %}
        {% case 'emails' %}
            {% include '_emails/includes/' ~ filepath %}
        {% case 'main' %}
            {% include '_main/includes/' ~ filepath %}
    {% endswitch %}
{% endmacro %}

One final example

For text truncation in my templates I tend to use eHouse Studio's Hacksaw plugin which works great.

Just like with dates, the output we want from the plugin is decided as part of the design process and we want to keep it consistent throughout our templates and with minimum thought required from template developers.

We might decide on a setup like the following (your parameters may vary):

{{ entry.richTextField | hacksaw(words='100', append='...') }}

Again we can wrap this within a macro to remove that logic from our templates:

{% macro truncate(text) %}
    {{ text | hacksaw(words='100', append='...') }}
{% endmacro %}

This comes with some benefits in addition to the logic removal.

  1. truncate makes a whole lot more sense than hacksaw.
  2. If you ever decide to use a different plugin or method to truncate text, you can simply update the macro without having to edit any of your other templates.

There is scope to allow some input if required. Maybe you need to allow the word limit to be optional just in case. No problem:

{% macro truncate(text, limit = 100) %}
    {{ text | hacksaw(words=limit, append='...') }}
{% endmacro %}

To use your truncate macro it's as simple as this:

{{ helpers.truncate(entry.richTextField, 70) }}

That's it! It'd be great to see how others are using macros to keep your Craft CMS templates clean and consistent. Share your own if you can!