Whitespace Control

Whitespace Control

By default, all text outside a {tag} is emitted verbatim. That means cleanly formatted templates — block tags on their own indented lines — emit extra blank lines and leading whitespace that may not belong in the output. Whitespace control trims this on a per-tag basis or globally.

What the engine controls

Whitespace control applies only to non-simple tags — the block-shaped or annotation-shaped tags that template authors typically indent on their own lines:

  • {#if} / {/if} — conditional block
  • {#each} / {/each} — iteration block
  • {#with} / {/with} — scope-shift block
  • {>} — partials
  • {!} — comments
  • {?var:} and {?:} — variables
  • {@} — debug tags

Simple bind tags ({Name}, {:myVar}) do not accept trim markers. Their position is part of the output by design.

Per-tag trim markers

A single - immediately after the open brace or immediately before the close brace activates trimming:

Marker placementEffect
{-#if …}Trim whitespace before the tag, not including newlines
{#if …-}Trim whitespace after the tag, including the first newline
{-#if …-}Trim on both sides

The asymmetry is deliberate: left-trim removes indentation in front of the tag while preserving line structure; right-trim absorbs the newline the author put after the tag for readability, so the next line of output starts where you want it.

Example

Data:
var person = new
{
    Certifications = new[] { "mcse", "mcitp", "mcts" },
    Name = new { First = "John", Last = "Doe" }
};
Without trim markers:
<p>Hello {Name.First}</p>
<div>
{#if Certifications}
<p>We see you have the following certs:</p>
<ul>
    {#each Certifications}
    <li>{$}</li>
    {/each}
</ul>
{/if}
{#if !Certifications}
We see you don't have any certs.
{/if}
</div>
Default output:
<p>Hello John</p>
<div>

<ul>
    
    <li>mcse</li>
    
    <li>mcitp</li>
    
    <li>mcts</li>
    
</ul>


</div>
With trim markers:
<p>Hello {Name.First}</p>
<div>
{-#if Certifications-}
<p>We see you have the following certs:</p>
<ul>
    {-#each Certifications-}
    <li>{$}</li>
    {-/each-}
</ul>
{-/if-}
{-#if !Certifications-}
We see you don't have any certs.
{-/if-}
</div>
Trimmed output:
<p>Hello John</p>
<div>
<ul>
    <li>mcse</li>
    <li>mcitp</li>
    <li>mcts</li>
</ul>
</div>

Global trim with TrimWhitespace = true

Marking every applicable tag with - markers is tedious for large templates. Setting TemplateEngine.TrimWhitespace = true applies the trim behavior to every eligible tag without needing markers:

ngin.TrimWhitespace = true;

Per-tag retain marker with +

When TrimWhitespace = true is in effect, the + retain marker opts a specific tag out of trimming. Placement mirrors the - marker:

Marker placementEffect
{+#each …}Retain whitespace before the tag
{#each …+}Retain whitespace after the tag
{+#each …+}Retain on both sides
Example:
ngin.TrimWhitespace = true;
Template:
<ul>
{+#each Items+}
  <li>{$}</li>
{+/each+}
</ul>
Result:
<ul>

  <li>A</li>

  <li>B</li>

</ul>

Without the + markers under TrimWhitespace = true, the same template would collapse to:

<ul>  <li>A</li>  <li>B</li></ul>

Choosing a strategy

For most output formats:

  • Free-form text or templates emitting whitespace-significant output — leave TrimWhitespace = false and add - markers only where needed.
  • HTML, JSON, SQL, code generation — set TrimWhitespace = true and add + markers in the rare spots that need preserved blank lines.

The two markers are independent: - and + are never combined on the same tag.

Next steps