{{craftSnippets}}
Home Articles Plugins Starter theme Components on Github Gists About
  • Home
  • Articles
  • Twig templating tips and tricks for Craft CMS
Posted on Oct 07, 2019 by Piotr Pogorzelski

Twig templating tips and tricks for Craft CMS

Collection of useful tips, tricks and code snippets that will help you with creating better Twig templates.
Table of contents:
  • Accessing config settings in templates
  • Sequential CSS animations with for loop
  • Replace string with regexp - using captured groups
  • Twig perversion
  • Local macro
  • Execution order and extends
  • Parsing and modifying HTML with retcon plugin
  • Multi-page articles
  • Using "set" with opening and closing tag
  • Adding condition to "for" loop
  • Using "else" in "for" loop
  • Generating input elements using "tag" function
  • Stopping image transforms from upscaling images
  • Generating BR HTML elements from newlines
  • Using build-in Craft static message translations
  • Using Twig code in Craft CMS fields content
  • Switch tag - using a single block of code for multiple conditions
  • Displaying template structure in rendered HTML code
  • Running code specific number of times

This article focuses mostly on usage of Twig as Craft CMS templating system - although non Craft users also might find here some useful bits of knowledge.

Accessing config settings in templates #

Settings in config/general.php file are available in Twig templates under {{craft.app.config.general}}. Pretty useful if you want to set some variables with global scope, available in all template files. I recommend however to keep such custom variables within a specific array, like this:

'theme' => [
    'google_analitics_id' => '123'
],

Thanks to that, you will not confuse them with built-in Craft config settings. Setting above would be available in twig under {{craft.app.config.general.theme.google_analitics_id}}.

Sequential CSS animations with for loop #

Let's say you want to apply sequential CSS animations on set of HTML elements - the animation of each element should be a bit delayed, compared to previous element animation. Here's a demonstration of how such animation can look like (source: "Creating smooth sequential animations with Sass" by Glenn McComb):

Delaying animation can be achieved with CSS animation-delay property. Using Twig loop.index variable available in for loop, we can provide value to this property which will be higher with each for loop iteration:

{% for entry in craft.entries.all() %}
    {% set seconds = loop.index/2 %}
    <div style="animation-delay: {{seconds}}s;">{{entry.title}}</div>
{% endfor %}

In the example above, each element of animation will be delayed by 0.5 seconds compared to the previous one.

Replace string with regexp - using captured groups #

replace filter can be used with regular expressions - which is mentioned in Craft documentation.

How about something undocumented? We can used captured groups (also called backreferences) in second argument of replace - in value that will replaces our regexp string. Let's use example from article about replacing email addresses in text with links:

{% set text = text|replace('/([a-zA-Z0-9_.+-]+)+@([a-zA-Z0-9-]+.[a-zA-Z]+)/', '<a ' ~ 'href="mailto:'~ '\\1&#64;\\2' ~'">'~ '\\1&#64;\\2' ~'</a>') %}
{{text|raw}}

In this example, there are two capture groups - fragment of email address before "@" character and after "@" character. In second argument of replace which determines what will replace the initial value, we invoke these two capture groups with \\1 and \\2 (&#64; between two of them is just HTML entity of "@").

Twig perversion #

Twig is intentionally limited with its syntax. It was done to discourage people from using it as something more than templating language. Business logic should live in Craft modules and plugins.

But what if you really want to use this while loop or === - type comparison operator? Twig perversion plugin got you covered. Still, you should consider using a module or plugin for more complicated things.

Local macro #

You can easily use Twig macro in the same file it was defined, without importing it - like this:

{% macro someMacro() %}
    {# something... %}
{% endmacro %}

{{ _self.someMacro() }}

It's useful when you want to define local "function" which will not be used outside a specific file.

Execution order and extends #

If you set Twig variable, it will be available below the place it was defined. This can lead to not so obvious results when using {% extends %} tag.

Lets's think of common setup - entry.twig file that extends base.twig file.

{# entry.twig #}
{% extends 'base.twig' %}

{% set class="some-class" %}

{% block body %}
some value...
{% endblock %}
{# base.twig #}
<html>
    <head>
        <title>Page</title>
    </head >
    <body class="{{class ?? null}}">
        {% block body %}{% endblock %}
    </body >
</html>

Craft first accessed entry.twig file. Then the variable was set and finally, the content was injected into blocks of base.twig that entry.twig was extending. Thanks to that, variable set in entry.twig was available in base.twig file.

You can read more about the execution order in The Twig processing order in Craft templates article on straight up Craft.

Parsing and modifying HTML with retcon plugin #

In an ideal world, we would have full control over HTML markup. Data queried from the database would contain just variables and string of text which we could wrap with HTML tags ourselves. This is often not the case. Sometimes data that Twig receives already contains HTML - for example when Redactor or other WYSWIG fields are used.

To manipulate such content, we can use Retcon plugin - it provides us with Twig filters for advanced HTML manipulation. Retcon plugin can add HTML attributes to specific tags, wrap them with other tags, extract specific tags content from chunks of HTML and do many other things.

Retcon syntax is symilar to jQuery. For example, this code will make all images with class "some-class" in HTML lazy loaded, by adding loading attribute with value lazy to them:

{{ some_html|retconAttr('img.some-class', { loading: 'lazy'}) }}

By comparision, here is jQuery code that accomplishes similar thing:

$('img.some-class').attr({'loading': 'lazy'})

Multi-page articles #

Nobody likes multi-page articles - except maybe advertisers who gain more ad views because visitors are forced to refresh page a few times more. Still, if you must, you can create a multi-page article in Craft CMS by paginating matrix blocks.

Let's think of entry containing matrix field with matrix_field handle:

{% set query = entry.matrix_field.limit(2) %}
{% paginate query as pageInfo, pageEntries %}

As you can see, paginating matrix blocks works exactly the same as paginating entries. More about pagination can be found in Craft documentation or in my article about ellipsis pagination component.

Using "set" with opening and closing tag #

This is set tag we all know and love:

{% set something = 'value' %}

What about setting variable to chunk of HTML? Do we need to set it like this?

{% set something = '<ul>
    <li><a href="#" id="current">Fruit</a>
        <ul>
            <li><a href="#">Apples</a></li>
            <li><a href="#">Oranges</a></li>
        </ul>
    </li>
</ul>' %}

Not really - we can use set with opening and closing tag instead:

{% set something %}
<ul>
    <li><a href="#" id="current">Fruit</a>
        <ul>
            <li><a href="#">Apples</a></li>
            <li><a href="#">Oranges</a></li>
        </ul>
    </li>
</ul>
{% endset %}

Adding condition to "for" loop #

You can attach a condition to for loop in order to execute code inside for only when specific conditions are met.

Think of a situation when you want output entries data into template - but only entries with images:

{% for entry in craft.entries.all() %}
    {% if entry.pictureField.exists() %}
        {{entry.title}}
    {% endif %}
{% endfor %}

Same effect can be achived with less verbose code if you use filter filter within for:

{% for entry in craft.entries.all()|filter(single => single.pictureField.exists()) %}
    {{entry.title}}
{% endfor %}

You can also use if keyword, but this approach is deprecated in favor of using filter filter.

{% for entry in craft.entries.all() if entry.pictureField.exists() %}
    {{entry.title}}
{% endfor %}

Using "else" in "for" loop #

When you loop through entries, you might want to display some message in case there are no entries to loop through - if the object provided to for is empty.

This can be done by using else tag inside for loop:

{% for single in entries.all() %}
    <a href="{{single.url}}">{{single.title}}</a>
{% else %}
    There are no entries!
{% endfor %}

Generating input elements using "tag" function #

You can generate HTML elements using tag() Twig function. This is especially useful in case of inputs, which often have lots of HTML attributes. Consider such input element:

<input type="text" class="input some-class" id="description" name="inputName" value="{{craft.app.request.getQueryParam['inputName']}}" required>

This doesn't look especially readable. Now, same input rendered using tag() function:

{{tag('input', {
    type: 'text',
    class: ['input', 'some-class'],
    id: 'description',
    name: 'inputName',
    value: craft.app.request.getQueryParam['inputName'],
    required: true
}) }}

Much better now.

Stopping image transforms from upscaling images #

Let's say that you use such image transform:

{% set transform = {
    width: 500,
    height: 500,
    mode: 'crop',
} %}
<img src="{{imageObject.getUrl(transform)}}">

It's all good unless transformed image has smaller dimensions then width and height set in transform setting. In such a situation, image may become pixelated because it will be up-scaled.

To avoid that, you can use min() Twig function:

{% set transform = {
    width: min(imageObject.width, 500),
    height: min(imageObject.height, 500),
    mode: 'crop',
} %}
<img src="{{imageObject.getUrl(transform)}}">

While using such transform settings, image will be scaled down if its dimensions are larger then transform settings, but will retain its original size if it's smaller.

You can also enable such functionality globally, without using min() function - by setting upscaleImages setting to false in config/general.php file.

Generating BR HTML elements from newlines #

If you put some text directly in Twig template, you need to use <br> HTML elements to put text into new line. Or do you?

nl2br Twig filter can be used to transform newlines (when you press enter during typing) into <br> elements. Now combine that with {% apply tag %} which can be used to apply Twig filters to large amount of markup.

{% apply nl2br %}
some text


some other text, two lines below
{% apply nl2br %}

Text visible in web browser that will be generated by this code, will have exactly the same newline layout as text in your Twig code.

Using build-in Craft static message translations #

Craft CMS has some built-in static translation files that are mainly used to translate the control panel. These translations can be used in your Twig code - thanks to that, your site will be automatically translated if its language is available as control panel translation.

To use Craft translations, add app as parameter of translation filter.

<button>{{'Search'|t('app')}}</button>

app is Craft's static message category. Every Craft plugin can also have its own category, so we might as well use their static message translations in Twig templates as well.

Using Twig code in Craft CMS fields content #

template_from_string() function allows you to parse string of text as Twig code. This means thay you can put Twig syntaxt like variables or functions in content stored in Craft CMS fields. Then you just need to parse this content using template_from_string() function.

{{template_from_string(entry.someField)}}

You can check out a nice example of this functionality in this tweet by nystudio107.

Switch tag - using a single block of code for multiple conditions #

Switch tag is extremely useful addition to Twig syntax introduced by Craft CMS. If you want to check for multiple values from a single {% case %} tag, separate the values with or operator.

{% switch variable %}

    {% case "abc" %}
    {# do something for variable with value "abc" #}

    {% case "xyz" or "123" %}
    {# do something for variable with value "xyz" or "123" #}

{% endswitch %}

Displaying template structure in rendered HTML code #

If you can't wrap your head around your complicated template structure when you look at rendered HTML code, Template Comments plugin might be a good solution for you. It adds HTML comments in places where Twig components are included and block tags are extended.

As plugins author said - "This can be especially handy when dealing with OPC (Other People's Code)".

Running code specific number of times #

If you want to run code specific number of times, you can use range function to create an array of specific length and use it in for loop:

{% for i in 1..5 %}
    {# run  this 5 times #}
{% endfor %}

Note the 1..5 syntax - it's just shorthand for range function.


TAGS:
#tutorial #twig
If you want to get latest updates on Craft CMS tutorials and components, follow me on Twitter or subscribe to RSS feed.
Articles on blog:
  • Frontend testing for Craft CMS websites with Codeception and Cypress
  • Building reactive Craft Commerce product page with Sprig plugin
  • Dynamically generated PDF attachments for Freeform submissions
  • Using template hooks in Craft CMS
  • Alpine JS modal component for Craft CMS
  • Using template UI elements to extend Craft CMS control panel
  • Matrix within a Matrix - possible solutions for Craft CMS
  • Universal email template for Craft CMS
  • Creating attributes table from entry fields in Craft CMS
  • Namespacing forms in Craft CMS
  • Creating map-based navigation for Craft CMS
  • Placeholder image macro for Craft CMS
  • Building AJAX contact form with Craft CMS
  • Using incognito field plugin for Craft CMS
  • Email footer creator made with Craft CMS
  • Infinite scrolling and lazy loading with Craft CMS
  • Using Javascript in Twig templates with Craft CMS
  • Twig templating tips and tricks for Craft CMS
  • Basic SEO functionality for Craft CMS
  • Working with dates in Craft CMS templates
  • Working with SVG images in Craft CMS templates
  • Responsive and lazy-loaded youtube videos with Craft CMS
  • Debugging and inspecting Twig templates in Craft CMS
  • Creating article excerpts with Twig component in Craft CMS
  • Adding favicons to Craft CMS website
  • Truncating text with Twig macros in Craft CMS
  • Universal language switcher for Craft CMS
  • Read time macro for Craft CMS
  • Using attr() function to render HTML attributes in Craft CMS
  • Building dynamic, AJAX based pagination for Craft CMS
  • How to add Disqus comments to Craft CMS website
  • Ellipsis pagination component for Craft CMS
  • Converting email addresses into links using Twig macro
  • Breadcrumb created from URL for Craft CMS
  • Best developer-oriented Craft CMS plugins
  • Search autocomplete component for Craft CMS
  • RSS feed - template component for Craft CMS
  • Testing emails sent by Craft CMS using Mailtrap
  • Quick edit link - Twig component for Craft CMS
  • Filtering entries in control panel using Searchit plugin
  • Fetching routes into Twig templates in Craft CMS


Frontend testing for Craft CMS websites with Codeception and Cypress

Building reactive Craft Commerce product page with Sprig plugin

Dynamically generated PDF attachments for Freeform submissions

Using template hooks in Craft CMS

Alpine JS modal component for Craft CMS

Using template UI elements to extend Craft CMS control panel

Matrix within a Matrix - possible solutions for Craft CMS

Universal email template for Craft CMS

Copyright ©2023 Piotr Pogorzelski