Fundamental ERB and Twig for Front-End Development
Henry Bley-Vroman, Former Senior UI Developer
Article Categories:
Posted on
Your comprehensive cross-reference guide for Twig and ERB front-end view templates.
Twig and ERB are the two front-end templating languages I use most when developing websites. Here I document the ways each write just about everything to build views: comments, conditionals, variables and undefined variables, interpolation, loops and the loop index, slicing, handling whitespace, retrieving an keyed values, and templating with blocks and partials. If you're familiar with one of Twig or ERB, use this as a cross-reference to translate your knowledge of the one language into the other. If you haven't used either, this should get you up and running quickly. Read on to learn more about ERB and Twig, or skip ahead to the code snippet reference section.
What is Twig?
Twig is SensioLabs' Django- / Jinja-like templating language for PHP. The recommended extension for Twig files is .twig
, .<compiled_extension>.twig
is useful, and .html
—though inaccurate— is common in front-end templating. It's used by SensioLabs' Symfony; by Drupal 8, which is built on Symfony; and by Craft, Viget's favorite pre-built CMS. (Read why it's our favorite here.)
Twig is a great language for building web front ends: it is full-featured without having more than one person could hope to learn, it reads fairly closely to English, and it has great official documentation. Twig is especially notable for its powerful support for complex inheritance across templates. Check out the use
tag, the embed
tag, and the block()
function.
Twig even has Javascript implementations, making it easy to fit into projects built on the JS ecosystem. A quick overview to help you pick the one that best suits yours needs:
- Mozilla's Nunjucks is officially "jinja2 inspired" but it has often followed Twig's lead and is now close enough to Twig that Blendid, Viget's build tool for painless local development of static, Craft, Drupal, or Rails sites uses it as Twig proxy (Nunjucks notably does not support Twig's horizontal
embed
inheritence). If you use Gulp in your build tools, you can use gulp-nunjucks. - Twig.js is a popular JS port of Twig that sees more active development than Nunjucks does. It does not reach full parity with Twig (as of this writing Twig.js notably still has some bugs with Twig's
embed
tag) but it currently comes closer than Nunjucks does and, since its goal is to duplicate Twig, it likely always will. The Twig.js Gulp plugin is gulp-twig. - Twing is a Twig engine for Node.js written in TypeScript which aims to always maintain complete parity with Twig. It is described as "a maintainability-first engine that passes 100% of the TwigPHP integration tests, is as close as possible to its code structure and expose an as-close-as-possible API." Because Twing is able to essentially reuse much of Twig's codebase, adding new features as they are merged into Twig is straightforward. Twing is the youngest of these projects… Twig users, show it your love! gulp-twing lets you use Twing with Gulp.
To learn Twig, read through the official documentation, and try things out in twigfiddle.
What is ERB?
ERB (Embedded Ruby) is a feature of Ruby that lets you —you guessed it!— embed Ruby in other files. ERB files have the extension .<compiled_extension>.erb
. It is the language HAML and Slim are shorthand for. ERB is commonly used for templating Views in Rails apps — at Viget we use it when building large sites with custom CMSes. (If that's something you do, check out Colonel Kurtz, the block editor we often use for the client-facing admin area of Rails app sites.)
Because it can do anything Ruby can do, it's extremely powerful, has a much steeper learning curve than Twig, and can do a lot that isn't relevant to front-end templating. There's no cannonical ERB-for-front-end-developers documentation, and the Rails official documentation is immense and hard to dig through. Some resources if for learning ERB:
- APIdock's ActionView::Layouts module documentation and their documentation for the "included modules" listed under ActionView::Helpers (the UrlHelper's
link...
andbutton_to
methods are essential. - The APIdock TagHelper's documentation for the
content tag
andtag
methods are useful. - If you're building forms familiarize yourself with the methods of the FormHelper, FormTagHelper, and FormOptionsHelper) (some will prefer the appearance of DevDocs — c.f. their ActionView and ActionView/Helpers documentation).
- LaunchSchool's Loops & Iterators is a good resource for understanding loops in Ruby.
- Mix & Go's How to use link_to in Rails is helpful for understanding
link_to
- RailsGuides' Action View Overview and Layouts and Rendering in Rails cover Rails views in depth, with clear examples.
ERB and Twig Snippets: Contents
Delimiters
Comments
Inline comments
ERB:
<%# … %>
<%# comment %>
Twig:
{# … #}
{# comment #}
Block comments
ERB:
=begin
…=end
the opening and closing tags must be at the start of the line
<% =begin %> block comment (both lines of both the begin and end tags must be at the start of their lines) <% =end %>
not
<% =begin %> not a comment <% =end %>
Twig:
{# … #}
{# block comment #}
or
not a comment {# block comment #} not a comment
Outputting values
ERB:
<%= … %>
<%= "print this" %> <%# output: `"print this"` %> <%= 1 + 2 %> <%# output: `3` %>
Twig:
{{ }}
{{ "print this" }} {# output: `print this` #} {{ 1 + 2 }} {# output: `3` #}
Execution (Control Code)
ERB:
<% … %>
<% if … do %> … <% end %>
Twig:
{% … %}
{% if … %} … {% endif %}
Conditionals
Single-statement conditionals
ERB:
if
andunless
<%= 2 if true %> <%# output: `2` %> <%= 2 if false %> <%# output: `nil` %> <%= 2 unless true %> <%# output: `nil` %> <%= 2 unless false %> <%# output: `2` %>
Multi-statement conditionals
ERB:
if
…elsif
…end
<%# assuming x, y, z, and n are defined %> <% if x %> y <% elsif z == n %> <%# note the spelling of elsif %> 0 <% else %> 1 <% end %>
Twig:
if
…elseif
…endif
{# assuming Twig's strict variables option is turned off OR x, y, z, and n are defined #} {% if x %} y {% elseif z == n %}{# note the spelling of elseif #} 0 {% else %} 1 {% endif %}
Conditionals with logical operators
Both ERB and Twig support "condition ?
iftrue :
iffalse", and "ifselftrue ?:
otherwise".
ERB. Note that the "then" case
:
must be provided<%# assuming x, y, z, and n are defined %> <%# if x then y %> <%# omitting the "else" will throw an error #> <%= x ? y : '' %> <%# if x is true, y. otherwise, if z equals n then 0. otherwise 1 %> <%= x ? y : z == n ? 0 : 1 %> <%# ternary operator: x if x is true, otherwise y %> <%= x ?: y %>
Twig
{# assuming x, y, z, and n are defined and/or Twig's strict variables option is turned off #} {# if x then y #} {{ x ? y }} {# if x is true, y. otherwise, if z equals n then 0. otherwise 1 #} {{ x ? y : z == n ? 0 : 1 }} {# ternary operator: x if x is true, otherwise y #} {{ x ?: y }}
Truth and falsity of zero in Boolean contexts
ERB:
0
isTrue
in Boolean contexts<%= false ? 'truthy' : 'falsy' %> <%# output: `"falsy"` %> <%= 0 ? 'truthy' : 'falsy' %> <%# output: `"truthy"` %>
Twig: as in PHP generally,
0
isFalse
in Boolean contexts{{ false ? 'truthy' : 'falsy' }} {# output: `falsy` #} {{ 0 ? 'truthy' : 'falsy' }} {# output: `falsy` #}
Defining variables
ERB:
=
<% var = 1 %> <% anotherVar = 0 %> <% falseVar = false %> <%= 2 if var %> <%# output: `2` %> <%= 2 if anotherVar %> <%# output: `2` %> <%= 2 if falseVar %> <%# output: `` %> <%= 2 unless falseVar %> <%# output: `2` %>
Twig:
set
{% set var = 1 %} {% set anotherVar = 0 %} {% set falseVar = false %} {{ var ? 2 }} {# output: `2` #} {{ anotherVar ? 2 }} {# output: null - Twig, unlike PHP, equates 0 with falsehood #} {{ falseVar ? '' : 2 }} {# output `2` #}
Twig can define multiple variables in a single call — just keep in mind that developers not used to this might overlook the multiple declarations!
{% set x, y, z = 1, 2, 3 %}
(A value must be explicitly provided for each variable:
{% set x, y = 1 %}
will error.)
Line breaks within a variable's value
ERB: multi-line blocks of markup can stored in an identifier with
content_for x do
…end
<% content_for longVar do %> <div> … </div> <% end %> <%= content_for(longVar) %>
Note:
content_for
is additive: each time you provide content for a given variable, that content is appeneded to what was there already. To usecontent_for
to overwrite a global variable, use theflush: true
option:<% content_for refreshedVar do %> a <% end %> <% content_for refreshedVar, flush: true do %> b <% end %>
Twig: use the
set
tag's formset x
…endset
to capture chunks of text{% set longVar %} <div> … </div> {% endset %} {{ longVar }}
Dealing with undefined variables
ERB:
-
defined?()
<%# output: the content if `var` is defined %> <% if defined?(var) %> … <% end %> <%# output: `var` if `var` is defined, otherwise `fallback` %> <%= defined?(var) ? var : fallback %>
-
||=
, the OR Equal operator<%# output: `var` if it is defined and not nil and not false, otherwise `fallback` %> <% var ||= fallback %> <% =begin %> common front-end use cases: 1. output a variable only if it is defined <% =end %> <% var ||= nil %> <%# set a variable with a fallback %> <% x = y ||= nil %>
-
Twig:
-
is defined
Especially useful when Twig's
strict variables
option is turned on, in which case referring to an undefined variable will throw an error.{# output: Twig_Error_Runtime: Variable "x" does not exist. #} {{ x }} {# output: the content if var is defined #} {% if var is defined %} … {% endif %} {# output: `advance` if var is defined, otherwise `fallback` #} {{ var is defined ? advance : fallback }}
-
??
, the null coalescing operator{# output: `var` if it is defined and not null, otherwise `fallback` #} {{ var ?? fallback }} {# common use cases: 1. output a variable only if it is defined #} {{ var ?? null }} {# set a variable with a fallback #} {% set x = y ?? null %}
-
Variable interpolation
ERB:
#{var}
<% x = 1 %> <%= "this is interpolated: #{x}" %><%# output: `this is interpolated: 1` %>
Twig:
#{var}
{% set x = 1 %} {{ "this is interpolated #{x}" }}{# output: `this is interpolated: 1` #}
Concatenation
ERB:
+
(plus). Note that to concatenate a string and a number in Ruby, the number must be converted to a string.<% string_variable = 'world' %> <% number_variable = 2 %> <%= 'hello ' + string_variable %> <%# output: `"hello world"` %> <%= "example #{number_variable}" %> <%# output: `"example 2"` %> <%= 'example ' + 3.to_s %> <%# output: `"example 3"` %>
Twig:
~
(tilde). Note that strings and numbers can be freely concatenated.{% set string_variable = 'world' %} {% set number_variable = 2 %} {{ 'hello ' ~ string_variable }} {# output: `hello world` #} {{ "example #{number_variable}" }} {# output: `example 2` #} {{ 'example ' ~ 3 }} {# output: `example 3` #}
Iteration (loops)
Iterating over items
ERB:
n.each do |i|
…end
<% items = ['a', 'b', 'c'] %> <%# output: `...` %> <% [0..items.length].each do %>.<% end %> <%# output: `a b c ` %> <% items.each do |item| %> <%= item %> <% end %>
Twig:
for i in n
…endfor
{% set items = ['a','b','c'] %} {# output: `...` #} {% for i in 0..items.length %}.{% endfor %} {# output: `a b c ` #} {% for item in items %} {{item}} {% endfor %}
Using the loop index, 0-indexed
ERB:
n.each_with_index do |i, index|
…end
<%# output: `0. a 1. b 2. c ` %> <% items = ['a', 'b', 'c'] %> <% items.each_with_index do |item,index| %> <%= index %>. <%= item %> <% end %>
n.times do |i|
…end
<%# output: `0 1 2 3 4 5 6 7 8 9` %> <% 10.times do |i| %><%= i %> <% end %>
Twig:
loop.index0
{% for item in items %} {{loop.index0}}. {{item}} {% endfor %}
Using the loop index, 1-indexed
ERB:
.each_with_index
'sindex
is always 0-indexed, so add1
<% items.each_with_index do |item,index| %> <%= index + 1 %>. <%= item %> <% end %>
n.times do |i|
…end
<%# output: `1 2 3 4 5 6 7 8 9 10 ` %> <% 10.times do |i| %><%= i %> <% end %>
Twig:
loop.index
{% for item in items %} {{loop.index}}. {{item}} {% endfor %}
Iterating a certain number of times
ERB:
n.times do |i|
…end
<% n = 3 %> <%# output: `...` %> <% n.times do %>.<% end %> <%# output: `1 2 3 ` %> <% n.times do |i| %> <%= i %> <% end %>
Twig:
for i in n
…endfor
{% set items = ['a','b','c'] %} {# output: `...` #} {% for i in 0..items.length %}.{% endfor %} {# output: `a b c ` #} {% for item in items %} {{item}} {% endfor %}
Inspecting data
ERB: several options for formatting an object's data, notably: simply outputting,
.inspect
ing, anddebug()
ing. For basic data-checking purposes in a view, the essential difference isdebug()
returns YAML whileinspect
and printing return strings.<%# for some object `posts` %> <%= posts %> <%= posts.inspect %> <%= debug(posts) %>
Twig:
The
|json_encode()
filter formats an object's data.{# for some object `posts` #} {{ posts|json_encode }}
The
dump()
function outputs information about a variable.{# for some object `posts` #} {{ dump(posts) }}
Note:
dump
must be enabled. Some implementations make it available out of the box (for example, Craft in dev mode).
Slicing
ERB:
.slice(index)
,.slice(start,count)
<%= [1,2,3,4].slice(1) %> <%# output: `2` %> <%= [1,2,3,4].slice(1,2) %> <%# output: `[2,3]` %>
Twig:
|slice(start,count)
or[start:count]
{{ [1,2,3,4]|slice(1) }} {# output: `Array` #} {{ [1,2,3,4]|slice(1,2) }} {# output: `Array` #}
Note: The output of the above Twig examples is
Array
, because in Twig the output of{{ [anArray] }}
isArray
. If you need to print an array, use|json_encode
:{{ [1,2,3,4]|slice(1)|json_encode() }} {# output: `[2,3,4]` #} {{ [1,2,3,4]|slice(1,2)|json_encode() }} {# output: `[2,3]` #}
In execution, no special steps are necessary:
{% set myArray = [1,2,3,4] %} …
Shorthand to slice the first count
items
ERB:
.take(count)
or.first(count)
<%= [1,2,3,4].take(2) %> <%# output: `[1,2]` %> <%= [1,2,3,4].first(2) %> <%# output: `[1,2]` %>
Twig:
[:count]
{{ [1,2,3,4][:2]|json_encode() }} {# output: `[1,2]` #}
Shorthand for everything after the start
item
Twig:
[start:]
{{ [1,2,3,4][2:]|json_encode() }} {# output: `[3,4]` #}
Trimming whitespace
ERB
If
trim_mode
is set to-
, a-
in the closingerb
tag will trim trailing whitespace:<% something -%> 1 <%= something_else -%> 2 <% another_thing %>
is equivalent to
<% something %>1 <%= something_else %>2 <% another_thing %>
Twig
Trim leading or trailing whitespace by adding a
-
inside in an opening or close delimiter, respectively:{% something -%} 1 {%- something_else -%} 2 {%- last_thing %}
is equivalent to
{% something %}1{% something_else %}2{% last_thing %}
Trimming space between HTML elements
Twig
Twig doesn't care what language you are compiling to, but it does provide a special
spaceless
tag for use with HTML.{% spaceless %} <div>…</div> <span>…</span> {% endspaceless %}
is equivalent to
<div>…</div><span>…</span>
Note that this
spaceless
has limited powers:it isn't recursive
{% spaceless %} <div> <div> … </div> <div> <span>…</span> {% endspaceless %}
is equivalent to
<div><div> … </div><div><span>…</span>
and content between HTML tags will disrupt it
{% spaceless %} <div>…</div> sorry, spaceless <span>…</span> {% endspaceless %}
is equivalent to
<div>…</div> sorry, spaceless <span>…</span>
Keyed values
ERB:
Use a Symbol
:property
to look up an operation on a Hash:<% myHash = {hello: 'world'} %> <%= myHash[:hello] %> <%# output: "world" %>
Twig:
Use dot notation or subscript syntax to access attributes of a variable:
{% set myVar = {hello: 'world'} %} {{ myVar.hello }} {# output: world #} {{ myVar['hello'] }} {# output: world #}
Vertical inheritance
For a layout
file that pulls in page
:
ERB:
content_for
in child,yield
in parentlayouts/layout.html.erb
<%= yield :myBlock %>
views/page.html.erb
<% content_for :myBlock do %> the content <% end %>
Twig:
block
+extends
in child,block
in parent.layout.html.twig
{% block myBlock '' %} {# or #} {% block myBlock %}{% endblock %} {# or #} {% block myBlock %}{% endblock myBlock %}
page.html.twig
{% extends 'layout.html.twig' %} {% block myBlock %} the content {% endblock %}
or if all the content is a variable x, page.html.twig
{% extends 'layout.html.twig' %} {% block myBlock x %}
or if all the content is a single string, page.html.twig
{% extends 'layout.html.twig' %} {% block myBlock "#{x} content" %} {# or #} {% extends 'layout.html.twig' %} {% block myBlock x ~ "content" %}
or if all the content is a single literal string, page.html.twig
{% extends 'layout.html.twig' %} {% block myBlock 'the content' %} {# or #} {% block myBlock "the content" %}
Vertical inheritance with default content in the parent
ERB
layouts/layout.html.erb
<% if content_for?(:my_content) %> <%= yield :my_content %> <% else %> default content <% end %>
views/page.html.erb
<% content_for :my_content do %> the content <% end %>
Twig
main.html.twig
{% block content %} default content {% block sub_content '' %} {% endblock %}
override-content.html.twig
{% extends 'main.html.twig' %} {% block content %} the content {% endblock %}
Result of override-content.html.twig:
default content
override-subcontent.html.twig
{% extends 'main.html.twig' %} {% block subcontent %} the sub-content {% endblock %}
Result of override-subcontent.html.twig:
default content the sub-content
Using partials
ERB:
render
will output the contents of another file<%= render 'path/to/x' %>
To pass values to the rendered file, define them:
<% a = 1 %> <% b = 2 %> <%= render 'path/to/x', a:a, b:b %> <%# in path/to/x a=1 and b=2 %>
If the rendered file expects different variable names, use those:
<% a = 1 %> <% b = 2 %> <%= render 'path/to/x', y:a, z:b %> <%# in path/to/x y=1 and z=2 %>
Twig:
include
tag{% include 'path/to/x' %}
include
function{{ include('path/to/x') }}
The
include
tag passes the entire parent context to the included file by default:{% set a = 1 %} {% set b = 2 %} {% include 'path/to/x' %} {# in path/to/x a=1 and b=2 #}
To pass only certain data, use
include with only
:{% set a = 1 %} {% set b = 2 %} {% include 'path/to/x' with {a:a} only %} {# in path/to/x a=1 and b does not exist #}
Rename variables in the
with
(can be combined withonly
):{% set a = 1 %} {% include 'path/to/x' with {y:a} %} {# in path/to/x a=1 and y=1 #} {% include 'path/to/z' with {y:a} only %} {# in path/to/z y=1 and a does not exist #}
Is something missing? Did a mistake slip in? Leave a comment and let me know!