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><script>alert('xss')</script></p>
Characters encoded:
- < → <
- > → >
- & → &
- " → "
- ' → '
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:
- Evaluate {{#ifEquals}} blocks: Check field values against literals
- Evaluate {{#if}} blocks: Check for non-empty values
- Evaluate {{#unless}} blocks: Check for empty values
- Replace {{markers}}: Inject HTML-encoded field values
- 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
- Component Structure — Overall component structure
- Dialog Schema — Define fields used in templates
- Validation Rules — Template validation requirements
- Component Example — Complete working example