A mostly reasonable approach to Twig
This is a superset of the Official SensioLabs Twig Standards.
-
2.1 String tokens to be used inside the
replace
filter should be marked with percentage signs.{# Bad #} {{ sidekicks|replace('{{ robin }}', 'Dick Grayson') }} {# Good #} {{ sidekicks|replace('%robin%', 'Jason Todd') }}
-
2.2 Do not use the default filter for default data.
Default data should come from context. Use default filter for logic only.
{# Bad #} {{ url|default('#') }} {# Good #} {{ url|default(data.url) }}
-
2.3 Use merge filter to add to arrays or objects.
Note that you need to make sure the array/object is iterable.
{# Bad #} {% include '@button' with { data: { text: data.button.text, icon: '#arrow-right' } } %} {# Good #} {% include '@button' with { data: data.button|merge({ icon: '#arrow-right' }) } %}
-
2.4 Escape data and values in HTML attributes with the
escape
filter.{# Bad #} <button data-name="{{ data.name }}"> {# Good #} <button data-name="{{ data.name|escape('html_attr') }}">
- 2.5 If possible, avoid transforming data with filters. Transform data beforehand, in PHP.
-
3.1 Use dump function to debug data.
Wrap the output with a
pre
tag to make it easier to read.<pre> {{ dump(user) }} </pre>
-
4.1 Do not use Math operators.
Math is calculation logic that should stay in PHP.
As an example, instead of the mod operator, use batching.
{# Bad #} {% for row in items %} {% if loop.index == 1 or (loop.index % 3) == 1 %} {# do something with every third item #} {% endif %} {% endfor %} {# Good #} {% for row in items|batch(3) %} {% for column in row %} {% if loop.index == 1 %} {# do something with every third item #} {% endif %} {% endfor %} {% endfor %}
-
5.1 Use variables to store common values in templates.
Instead of doing the same template logic in multiple instances, save the values to variables with
set
tags.
-
6.1 Use Twig comments to convey important implementation information to other developers.
Twig comments will be compiled away, HTML comments are transferred and visible in source to all users.
{# Bad #} <!-- TODO: id should not be static --> <input id="input-1" /> {# Good #} {# TODO: id should not be static #} <input id="input-1" />
-
9.1 Place 1 space before the leading brace.
{# Bad #} {% set dog={ 'age': '1 year', 'breed': 'Bernese Mountain Dog', } %}; {# Good #} {% set dog = { 'age': '1 year', 'breed': 'Bernese Mountain Dog', } %};
-
9.2 Place 1 space before the opening parenthesis in control statements (
if
,for
etc.). Place no space between the argument list and the function name in function calls and declarations.{# Bad #} {% if(isJedi) %} {% set enemy = 'sith' %} {% endif %} {# Good #} {% if (isJedi) %} {% set enemy = 'sith' %} {% endif %} {# Bad #} {{ jedi|default ('Yoda') }} {# Good #} {{ jedi|default('Yoda') }}
-
9.3 Set off operators with spaces.
{# Bad #} {% set x=y+5 %} {# Good #} {% set x = y + 5 %}
-
9.4 End files with a single newline character.
{# Bad #} {{ stuff }}
{# Bad #} {{ stuff }}↵ ↵
{# Good #} {{ stuff }}↵
-
9.5 Do not pad your blocks with blank lines.
{# Bad #} {% set foo = 'bar' %} {# Bad #} {% if baz %} {{ qux }} {% elseif %} {{ foo }} {% endif %} {# Good #} {% if baz %} {{ qux }} {% elseif %} {{ foo }} {% endif %}
-
9.6 Do not add spaces inside parentheses.
{# Bad #} {{ foo|default( 'foo' ) }} {# Good #} {{ foo|default('foo') }}
-
9.7 Do not add spaces inside brackets.
{# Bad #} {% set foo = [ 1, 2, 3 ] %} {{ foo.0 }} {# Good #} {% set foo = [1, 2, 3] %} {{ foo.0 }}
-
9.8 Add spaces inside curly braces.
{# Bad #} {% set foo = {clark: 'kent'} %} {# Good #} {% set foo = { clark: 'kent' } %}
-
9.9 Avoid having everything in one line.
Why? This ensures readability and maintainability.
{# Bad #} {% if victory|default %}{{ congratulations }}{% else %}{{ failure }}{% endif %} {# Good #} {% if victory|default %} {{ congratulations }} {% else %} {{ failure }} {% endif %}
-
9.10 Avoid having too much attributes in single line.
Why? This ensures readability and maintainability.
{# Bad #} <img loading="lazy" src="{{ placeholderSrc }}" data-srcset="{{ data.srcset }}" data-sizes="auto" alt="{{ data.alt }}" {% if data.width %}width="{{ data.width }}"{% endif %} {% if data.title %}title="{{ data.title }}"{% endif %} class="image__img lazyload"> {# Good #} <img loading="lazy" src="{{ placeholderSrc }}" data-srcset="{{ data.srcset }}" data-sizes="auto" alt="{{ data.alt }}" {% if data.width %}width="{{ data.width }}"{% endif %} {% if data.title %}title="{{ data.title }}"{% endif %} class="image__img lazyload" >
-
9.11 Avoid having too much logic in a single HTML attribute.
Why? This ensures readability and maintainability.
Instead, split the logic to a separate variable block with whitespace control.
{# Bad #} <div class="textfield{% if modifier %} {{ modifier }}{% endif %}{% if class %} {{ class }}{% endif %}{% if data.isInvalid %} is-invalid{% endif %}{% if data.isDisabled %} is-disabled{% endif %}{% if data.icon %} textfield--icon{% endif %}"></div> {# Good #} {% set BEM -%} textfield {% if modifier %} {{ modifier }}{% endif %} {%- if class %} {{ class }}{% endif %} {%- if data.isInvalid %} is-invalid{% endif %} {%- if data.isDisabled %} is-disabled{% endif %} {%- if data.icon %} textfield--icon{% endif %} {% endset %} <div class="{{ BEM }}"></div>
-
9.12 Indenting nested logic and blovks
For consistency, everything that is nested should be indented by one level more than its context. (Even if it breaks HTML indentation)
{# Bad #} {% if data.title %} {{ data.title }} {% endif %} <div class="row__one">{{ data.rowOne }}</div> {% if data.rowTwo %} <div class="row__two">{{ data.rowTwo }}</div> {% endif %} {# Good #} {% if data.title %} {{ data.title }} {% endif %} <div class="row__one">{{ data.rowOne }}</div> {% if data.rowTwo %} <div class="row__two">{{ data.rowTwo }}</div> {% endif %}
-
10.1 Leading commas: No.
{# Bad #} {% set story = [ 'once' , 'upon' , 'a' , 'time' ] %} {# Good #} {% set story = [ once, upon, aTime, ] %} {# Bad #} {% set hero = { 'firstName': 'Ada' , 'lastName': 'Lovelace' , 'birthYear': '1815' , 'superPower': 'computers' } %} {# Good #} {% set hero = { 'firstName': 'Ada', 'lastName': 'Lovelace', 'birthYear': '1815', 'superPower': 'computers' } %}
-
10.2 Use double quotes for HTML attributes, single quotes for everything else.
{# Bad #} <input value='bad' /> {# Good #} <input value="good" /> <input value="\"good\"" /> {# Bad #} {% set hero = "bad" %} {# Good #} {% set hero = 'good' %}
-
11.1 Use camelCase to name variables.
{# Bad #} {% set story_of_my_life = 'cry baby cry' %} {% set storyofmylife = 'cry baby cry' %} {# Good #} {% set storyOfMyLife = 'cry baby cry' %}
You can use majority of twig functions, but there are some restrictions in styleguide, because we use Twig.js adapter.
-
13.1 Include inside macro not working very well. Example:
{% macro li(item, class, icon) %} <li class="pagination__item {{ class }}"> <a href="{{ item.url }}" class="pagination__link"> {% include '@icon' with { name: icon } %} {{ item.text }} </a> </li> {% endmacro %} {{ ul.li(data.item, 'pagination__item--first', 'arrow') }}
You can fix this issue by sending whole icon component into macro. Example:
{% macro li(item, class, icon) %} <li class="pagination__item {{ class }}"> <a href="{{ item.url }}" class="pagination__link"> {{ icon }} {{ item.text }} </a> </li> {% endmacro %} {% set icon %} {% include '@icon' with { name: 'arrow' } %} {% endset %} {{ ul.li(data.item, 'pagination__item--first', icon) }}
It's not nice but will work.
- 14.1 When deciding to choose whether to use a certain Twig functionality, make sure it is supported by both Twig PHP and Twig.js as we generally use both in our projects.
-
14.2 If data existence is questionable, use twig if tag.
{# Bad #} <div class="textfield__error"> {{ data.error }} </div> {# Bad #} <div class="textfield__error"> {% if data.error %} {{ data.error }} {% endif %} </div> {# Good (no unnecessary html) #} {% if data.error %} <div class="textfield__error"> {{ data.error }} </div> {% endif %}
-
14.3 Use data.something only if data really comes from back-end and is changeable
Example if button icon is fixed and admin can't change it. <button type="button"> {{ data.text }} {% include '@icon' with { name: icon, class: 'button__icon', modifier: '' } %} </button>
-
15.1 Use semantic HTML elements, wherever appropriate.
This is to ensure basic usability of the website for all people.
{# Bad #} {# Clickable div that does not have role button nor is tabbable. #} <div onclick=""> {{ data.text }} </div> <a href="#">this is a button that has interactivity via JS and no real href</a> {# Good #} <button onclick=""> {{ data.text }} </button> <button>this is a button that has interactivity via JS</button>
-
15.2 Use appropriate landmarks to convey important parts of the website to assistive tech.
See examples in WAI-ARIA Authoring Practises.
-
15.3 Make sure your content order is semantic.
Sometimes due to design quirks HTML structure cannot be semantically correct. In these cases you can hide the wrongfully placed elements using
aria-hidden="true"
and add visually hidden elements to semantically appropriate places, thus making content order correct and accessible.{# Bad #} <section> <div class="content">...</div> <h2>Section title</h2> <div class="content">...</div> </section> {# Better #} <section> <h2 class="h-visually-hidden">Section title</h2> <div class="content">...</div> <h2 aria-hidden="true">Section title</h2> <div class="content">...</div> </section> {# Best #} {# Work with your designer to make sure the design order follows semantic order! #} <section> <h2>Section title</h2> <div class="content">...</div> <div class="content">...</div> </section>
-
15.4 Use appropriate ARIA behavior on interactive components that do not exist natively in HTML.
For example: alerts, dialogs, tooltips, accordions, tabs, autocomplete inputs do not have native HTML elements that convey their behavior and semantics to assistive technology.
These kinds of interactive components need to have appropriate ARIA roles and JavaScript functionality to work properly for everyone.
Please explore the WAI-ARIA Authoring Practises document for specific examples and explanations.
-
15.5 Avoid using
target="_blank"
on links.Do not force people to new tabs/windows when it is not strictly necessary. People who want to open links in new tabs usually know how to do this themselves.
See this CSS-Tricks article for more explanations.
If you absolutely must use
target="_blank"
on links, userel="noreferrer
with it.