Choose between two pieces of markup with #if(…) {# and #} else {#.
The condition is an ordinary JavaScript expression and the branches are ordinary markup. A record with a title renders one way, a record without renders a placeholder — which is the everyday reason to branch inside a template.
<style>
body { font: 14px/1.6 system-ui, sans-serif; margin: 0; color: #24292f; }
ul { list-style: none; padding: 0; margin: 0; }
li { padding: 8px 0; border-bottom: 1px solid #eaeef2; display: flex; gap: 10px; align-items: baseline; }
li:last-child { border-bottom: 0; }
.name { font-weight: 600; min-width: 9em; }
.muted { color: #8c959f; font-style: italic; }
</style>
<script type="text/template" id="team">
<ul>
#for(let m of members) {#
<li>
<span class="name">#m.name#</span>
#if(m.title) {#
<span class="title">#m.title#</span>
#} else {#
<span class="title muted">No title on file</span>
#}#
</li>
#}#
</ul>
</script>
<div id="output"></div>
<script src="https://cdn.jsdelivr.net/gh/richdafunk/hashJS@v1.3.6/hashJS.js"></script>
<script>
const data = {
members: [
{ name: 'Ada Lovelace', title: 'Principal engineer' },
{ name: 'Grace Hopper', title: 'Compiler lead' },
{ name: 'Karen Sparck', title: '' },
{ name: 'Alan Turing', title: 'Research' }
]
};
new hashJS('team', data, 'output');
</script> Note how the middle of the chain is written: #} else {# is a single tag, not #}# followed by #else {#. Anything ending in { is treated as a block opener and anything beginning with } as a continuation of the block before it. The library never learns the keywords, which is why arbitrary control flow works — try/catch and labelled blocks follow the same rule without anyone adding support for them.
The condition is truthiness, not a comparison: an empty string is falsy, so #if(m.title) covers both a missing property and a blank one.