Templating Syntax

Component templates use a simple marker-based syntax to inject dialog values into HTML. This guide covers all templating features and best practices.

Variable Markers

Variable markers inject dialog field values into HTML:

<h1>{{heading}}</h1>

<p>{{description}}</p>

Syntax

  • Opening: {{
  • Field name: Must match a field name from dialog.json
  • Closing: }}

HTML Encoding

All variable values are automatically HTML-encoded to prevent XSS attacks:

<!-- Dialog value: <script>alert('xss')</script> -->

<p>{{userInput}}</p>

 

<!-- Rendered output: -->

<p>&lt;script&gt;alert('xss')&lt;/script&gt;</p>

Characters encoded:

  • < → &lt;
  • > → &gt;
  • & → &amp;
  • " → &quot;
  • ' → &#39;

Empty Values

If a field value is empty or undefined, the marker is replaced with an empty string:

<!-- Dialog value: description = "" -->

<p>{{description}}</p>

 

<!-- Rendered output: -->

<p></p>

Conditional Blocks

Conditional blocks show or hide content based on field values.

If Block

Show content when a field has a non-empty value:

{{#if description}}

  <p class="description">{{description}}</p>

{{/if}}

Behavior:

  • Content is included if the field value is a non-empty string
  • Content is excluded if the field value is empty, null, or undefined

Example:

<!-- Dialog values: { showSubtitle: true, subtitle: "Welcome" } -->

{{#if subtitle}}

  <h2>{{subtitle}}</h2>

{{/if}}

 

<!-- Rendered output: -->

<h2>Welcome</h2>

<!-- Dialog values: { subtitle: "" } -->

{{#if subtitle}}

  <h2>{{subtitle}}</h2>

{{/if}}

 

<!-- Rendered output: (empty) -->

Unless Block

Show content when a field is empty:

{{#unless description}}

  <p class="placeholder">No description provided</p>

{{/unless}}

Behavior:

  • Content is included if the field value is empty, null, or undefined
  • Content is excluded if the field value is a non-empty string

Example:

<!-- Dialog values: { description: "" } -->

{{#unless description}}

  <p>No description available</p>

{{/unless}}

 

<!-- Rendered output: -->

<p>No description available</p>

If Equals Block

Show content when a field equals a specific value:

{{#ifEquals alignment "center"}}

  <div class="text-center">

    <h1>{{heading}}</h1>

  </div>

{{/ifEquals}}

Behavior:

  • Content is included if the field value exactly matches the literal string
  • Comparison is case-sensitive
  • Works with any field type (text, select, etc.)

Example:

<!-- Dialog values: { theme: "dark" } -->

{{#ifEquals theme "dark"}}

  <div class="dark-theme">

    <p>{{content}}</p>

  </div>

{{/ifEquals}}

 

{{#ifEquals theme "light"}}

  <div class="light-theme">

    <p>{{content}}</p>

  </div>

{{/ifEquals}}

 

<!-- Rendered output: -->

<div class="dark-theme">

  <p>Content here</p>

</div>

Nested Conditionals

Conditionals can be nested:

{{#if showButton}}

  <div class="button-container">

    {{#ifEquals buttonStyle "primary"}}

      <button class="btn-primary">{{buttonText}}</button>

    {{/ifEquals}}

    {{#ifEquals buttonStyle "secondary"}}

      <button class="btn-secondary">{{buttonText}}</button>

    {{/ifEquals}}

  </div>

{{/if}}

Best practices:

  • Keep nesting shallow (2-3 levels maximum)
  • Consider using CSS classes instead of nested conditionals
  • Test all conditional paths

Image Fields

Image fields store values as JSON objects with src and alt properties:

<img src="{{image.src}}" alt="{{image.alt}}" />

Dialog value:

{

  "image": {

    "src": "https://example.com/photo.jpg",

    "alt": "A beautiful sunset"

  }

}

Rendered output:

<img src="https://example.com/photo.jpg" alt="A beautiful sunset" />

Conditional Images

Check if an image is set:

{{#if image.src}}

  <figure>

    <img src="{{image.src}}" alt="{{image.alt}}" />

    {{#if image.alt}}

      <figcaption>{{image.alt}}</figcaption>

    {{/if}}

  </figure>

{{/if}}

Accessibility

Always include alt text:

<!-- Good: Alt text provided -->

<img src="{{image.src}}" alt="{{image.alt}}" />

 

<!-- Good: Empty alt for decorative images -->

<img src="{{decorativeImage.src}}" alt="" />

 

<!-- Bad: Missing alt attribute -->

<img src="{{image.src}}" />

Page Links

Page link fields store page slugs:

<a href="{{relatedPage}}">Read more</a>

Dialog value:

{

  "relatedPage": "/blog/my-post"

}

Rendered output:

<a href="/blog/my-post">Read more</a>

Conditional Links

Check if a link is set:

{{#if relatedPage}}

  <a href="{{relatedPage}}" class="link">{{linkText}}</a>

{{/if}}

Boolean Fields

Boolean fields store true or false as strings:

{{#ifEquals showBorder "true"}}

  <div class="with-border">

    {{content}}

  </div>

{{/ifEquals}}

Note: Boolean values are stored as the strings "true" or "false", not as actual booleans.

Rich Text Fields

Rich text fields contain HTML markup:

<div class="content">

  {{richContent}}

</div>

Dialog value:

<p>This is <strong>bold</strong> and <em>italic</em> text.</p>

Rendered output:

<div class="content">

  <p>This is <strong>bold</strong> and <em>italic</em> text.</p>

</div>

Note: Rich text is sanitized during component upload to remove dangerous HTML.

Unresolved Markers

If a marker references a field that doesn't exist in the dialog, it's replaced with an empty string:

<!-- Template: -->

<p>{{nonExistentField}}</p>

 

<!-- Rendered output: -->

<p></p>

Validation: The CMS validates that all markers reference declared fields during component upload.

Whitespace Handling

Whitespace around markers is preserved:

<!-- Template: -->

<p>Hello {{name}}, welcome!</p>

 

<!-- Dialog value: { name: "Alice" } -->

<!-- Rendered output: -->

<p>Hello Alice, welcome!</p>

Whitespace inside conditional blocks is preserved:

<!-- Template: -->

{{#if description}}

  <p>{{description}}</p>

{{/if}}

 

<!-- Rendered output (if description is set): -->

  <p>Description text</p>

Comments

HTML comments are preserved in the output:

<!-- This is a comment -->

<div>{{content}}</div>

 

<!-- Rendered output: -->

<!-- This is a comment -->

<div>Content here</div>

Best Practices

Marker Naming

  • Use descriptive field names: heading, description, buttonText
  • Use camelCase: showButton, primaryColor
  • Avoid abbreviations: description not desc

Conditionals

  • Use #if for presence checks: Check if a field has a value
  • Use #unless for absence checks: Show fallback content
  • Use #ifEquals for specific values: Check against literal values
  • Keep logic simple: Complex logic belongs in CSS or JavaScript

Accessibility

  • Always include alt text: Use {{image.alt}} for all images
  • Use semantic HTML: <button>, <nav>, <article>, etc.
  • Provide fallbacks: Show meaningful content when fields are empty

Performance

  • Minimize conditionals: Each conditional adds processing overhead
  • Use CSS for styling: Don't use conditionals for visual variations
  • Keep templates small: Large templates slow down rendering

Testing

  • Test all paths: Try all conditional branches
  • Test empty values: Verify behavior with empty fields
  • Test special characters: Ensure HTML encoding works correctly

Example: Complete Template

<article class="card {{#ifEquals theme "dark"}}card-dark{{/ifEquals}}">

  {{#if image.src}}

    <div class="card-image">

      <img src="{{image.src}}" alt="{{image.alt}}" />

    </div>

  {{/if}}

  

  <div class="card-content">

    <h2 class="card-title">{{title}}</h2>

    

    {{#if description}}

      <p class="card-description">{{description}}</p>

    {{/if}}

    

    {{#unless description}}

      <p class="card-placeholder">No description available</p>

    {{/unless}}

    

    {{#if showButton}}

      <div class="card-actions">

        {{#if buttonLink}}

          <a href="{{buttonLink}}" class="btn {{#ifEquals buttonStyle "primary"}}btn-primary{{/ifEquals}}{{#ifEquals buttonStyle "secondary"}}btn-secondary{{/ifEquals}}">

            {{buttonText}}

          </a>

        {{/if}}

      </div>

    {{/if}}

  </div>

</article>

Rendering Process

The component renderer processes templates in this order:

  1. Evaluate {{#ifEquals}} blocks: Check field values against literals
  2. Evaluate {{#if}} blocks: Check for non-empty values
  3. Evaluate {{#unless}} blocks: Check for empty values
  4. Replace {{markers}}: Inject HTML-encoded field values
  5. Replace unresolved markers: Replace any remaining markers with empty strings

This order ensures conditionals are evaluated before variable substitution.

Limitations

No Loops

The templating system does not support loops. For repeating content, use:

  • Multiple components (add the same component multiple times)
  • Data fetching with component.js (for dynamic lists)

No Expressions

The templating system does not support expressions or calculations:

<!-- Not supported: -->

<p>Total: {{price * quantity}}</p>

 

<!-- Instead, calculate in component.js: -->

<p>Total: {{total}}</p>

No Filters

The templating system does not support filters or transformations:

<!-- Not supported: -->

<p>{{description | uppercase}}</p>

 

<!-- Instead, use CSS: -->

<p class="uppercase">{{description}}</p>

Related Topics