Conditionals
Introduction
Conditional rendering in Ree uses {#if} blocks. The condition is plain JavaScript - any expression that evaluates to a truthy value enters the block, anything falsy skips it. The same rules JavaScript uses everywhere else apply here: 0, "", null, undefined, false, and NaN are falsy; everything else is truthy.
The if Block
{#if props.user }
<p>Welcome back, {= props.user.display_name }.</p>
{/if}
Every {#if} must be closed by a matching {/if}. If you forget, the engine throws a clear error at compile time pointing to the unclosed block.
Conditions are also resilient at render time: if the expression throws - say you reach through a property of something that turned out to be undefined - the engine catches it and treats the condition as falsy rather than crashing the whole render. So {#if props.user.profile.verified } renders the {:else} branch (or nothing) when props.user.profile is missing, instead of erroring. Treat this as a safety net, not a license to skip optional chaining - props.user?.profile?.verified still states intent more clearly and is the recommended form.
if / else
Add an {:else} branch for the falsy case:
{#if props.user }
<p>Welcome back, {= props.user.display_name }.</p>
{:else}
<a href="/login">Log in</a>
{/if}
Only one {:else} is allowed per {#if}. There is no {:elseif} keyword - chain conditions with nested blocks or compute the value with {{ ... }} first.
Multiple Conditions
For more than two branches, the cleanest approach is to compute a label or a class in a {{ ... }} block and output it:
{{ const label = props.status === "draft" ? "Draft" : props.status === "review" ? "In review" : props.status === "live"
? "Live" : "Unknown"; }} <span class="badge">{= label }</span>
If you need different markup per branch rather than different text, nest the blocks:
{#if props.record.status === "draft" }
<button class="primary">Submit for review</button>
{:else} {#if props.record.status === "review" }
<button class="secondary">Approve</button>
{:else}
<span class="text-muted">Already live</span>
{/if} {/if}
This reads awkwardly past two levels - that's usually a sign that the branching belongs in the handler rather than the template. Compute the right state on the server, pass a single field through props, and let the template render one shape.
Conditions Inside Attributes
{#if} works inline as well as in element content. A common case is conditionally adding an attribute:
<option value="{= option.value }" {#if option.value === props.selected }selected{/if}>
{= option.label }
</option>
<button class="primary" {#if props.submitting }disabled{/if}>
Save
</button>
For conditional class names specifically, building the string with a {{ ... }} block is often more readable than scattering {#if} tags through a class="..." attribute:
{{ const row_class = props.record.is_archived ? "row archived" : "row" }}
<tr class="{= row_class }">
...
</tr>
Truthiness Pitfalls
The condition uses regular JavaScript truthiness, which catches a few people:
- An empty array (
[]) is truthy. Useprops.records.length > 0if you only want the block when there are items. - An empty string (
"") is falsy. If a database column might return""for a missing value,{#if props.field }will treat that as missing - which is usually what you want, but worth being aware of. - The number
0is falsy. For numeric IDs starting from0, checkprops.id != nullrather thanprops.id.
The {#each} block has its own {:else} branch for the "list is empty" case, so you rarely need {#if props.records.length > 0 } around an each block - write the {:else} inline instead. See Loops.
Negation and Nullish Coalescing
Plain JS operators are the right tool - there is no special syntax for "if not":
{#if !props.user }
<a href="/login">Log in</a>
{/if} {#if props.user && props.user.modules_tags?.split(",").includes("admin") }
<a href="/admin">Admin panel</a>
{/if}
Optional chaining (?.) and the nullish coalescing operator (??) work everywhere expressions are accepted - inside conditions, inside {= }, and inside {{ ... }}.