Skip to main content
Theme Raed uses Twig as its templating engine. Twig provides a clean, powerful syntax for building dynamic pages.

Template structure

Templates are organized in a hierarchical structure:
src/views/
├── layouts/           # Base layouts
│   ├── master.twig    # Main layout
│   └── customer.twig  # Customer area layout
├── pages/             # Page templates
│   ├── index.twig     # Home page
│   ├── cart.twig      # Cart page
│   └── product/       # Product pages
│       └── single.twig
└── components/        # Reusable components
    ├── header/
    ├── footer/
    └── home/

Template inheritance

Master layout

The base layout (layouts/master.twig) defines the HTML structure:
<!DOCTYPE html>
<html lang="{{ user.language.code }}" dir="{{ user.language.dir }}">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
  {% block head_scripts %}{% endblock %}
  {% block styles %}{% endblock %}
  
  <link rel="stylesheet" href="{{ 'app.css' | asset }}">
</head>
<body>
  <div class="app-inner flex flex-col min-h-full">
    {% component 'header.header' %}
    
    <main id="main-content" role="main">
      {% block content %}{% endblock %}
    </main>
    
    {% component 'footer.footer' %}
  </div>
  
  {% block scripts %}{% endblock %}
</body>
</html>

Extending layouts

Pages extend the master layout:
{% extends "layouts.master" %}

{% block content %}
  <div class="container">
    <h1>{{ page.title }}</h1>
    <!-- Page content -->
  </div>
{% endblock %}

{% block scripts %}
  <script defer src="{{ 'page.js' | asset }}"></script>
{% endblock %}

Syntax basics

Output variables

{{ store.name }}
{{ product.price }}
{{ user.language.name }}

Control structures

Conditionals

{% if product.is_on_sale %}
  <span class="text-red-800">{{ product.sale_price|money }}</span>
  <span class="line-through">{{ product.regular_price|money }}</span>
{% else %}
  <span>{{ product.price|money }}</span>
{% endif %}

Loops

{% for item in cart.items %}
  <div class="cart-item">
    <h3>{{ item.product_name }}</h3>
    <p>{{ item.price|money }}</p>
  </div>
{% endfor %}

Loop helpers

VariableDescription
loop.indexCurrent iteration (1-indexed)
loop.index0Current iteration (0-indexed)
loop.firstTrue if first iteration
loop.lastTrue if last iteration
loop.lengthTotal number of items
loop.parentParent context in nested loops

Components

Including components

{% component 'header.header' %}
{% component 'footer.footer' %}
{% component home %}

Including partials

{% include 'pages.partials.product.options' %}

Blocks

Blocks define sections that child templates can override:
{% block content %}
  Default content
{% endblock %}

Variables

Setting variables

{% set total_price = product.price * quantity %}
{% set has_many_images = product.images|length > 1 %}

Theme settings

{{ theme.settings.set('placeholder', 'images/placeholder.png') }}

Comments

{# This is a comment #}

Hooks

Salla hooks allow injecting custom code at specific points:
{% hook 'head:start' %}
{% hook head %}
{% hook 'head:end' %}

{% hook 'body:start' %}
{% hook 'body:end' %}

{% hook 'cart:items.start' %}
{% hook 'cart:items.end' %}

{% hook 'product:single.form.start' %}
{% hook 'product:single.form.end' %}
Hooks enable merchants to add custom content without modifying theme files.

Whitespace control

Control whitespace around tags:
{%- if condition -%}
  Content
{%- endif -%}

Ternary operator

{{ product.is_on_sale ? 'On sale' : 'Regular price' }}
{{ user.type == 'guest' ? 'Sign in' : 'Account' }}

<div class="{{ product.is_on_sale ? 'sale-badge' : 'hidden' }}">
  Sale!
</div>

Null coalescing

{{ product.subtitle ?? 'No subtitle' }}
{{ image.alt ?? product.name }}
{{ theme.settings.get('imageZoom') ?? 'enabled' }}

String concatenation

{{ 'product-' ~ product.id }}
{{ 'details-slider-' ~ product.id }}
{{ store.url ~ '/cart' }}

Array operations

Array length

{% if product.images|length > 1 %}
  <div class="image-gallery">...</div>
{% endif %}

Array slicing

{% for product in section.products|slice(0, 4) %}
  <!-- Show first 4 products -->
{% endfor %}

Array contains

{% if 'sms' in product.notify_availability.channels %}
  <input type="checkbox" name="channel[]" value="sms">
{% endif %}

Object property access

{{ store.name }}
{{ product.brand.logo }}
{{ cart.items[0].product_name }}

Best practices

Keep logic minimal in templates. Use variables and filters instead of complex expressions.

Use semantic structure

<!-- Good: Clear structure -->
{% set is_on_sale = product.is_on_sale %}
{% set show_quantity = not product.is_hidden_quantity %}

{% if is_on_sale %}
  <!-- Sale price display -->
{% endif %}

<!-- Avoid: Complex inline logic -->
{% if product.sale_price and product.sale_price < product.regular_price %}
  <!-- Hard to read -->
{% endif %}

Optimize loops

<!-- Good: Check before looping -->
{% if products|length %}
  {% for product in products %}
    <!-- Product card -->
  {% endfor %}
{% endif %}

<!-- Better: Use for-else -->
{% for product in products %}
  <!-- Product card -->
{% else %}
  <p>No products</p>
{% endfor %}

Use lazy loading

{% for image in product.images %}
  <img
    src="{{ image.url }}"
    {% if loop.first %}
      loading="eager"
      fetchpriority="high"
    {% else %}
      loading="lazy"
    {% endif %}
  >
{% endfor %}

Build docs developers (and LLMs) love