Conditionals

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 - for multi-way branching, chain conditions with nested blocks, compute the value with {{ ... }} first, or use {#switch} for value dispatch.

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.

Value Dispatch with {#switch}

When the branches are selected by the value of one expression, {#switch} is cleaner than nested {#if} chains - it is the {:else if} chain the engine deliberately does not support:

{#switch props.record.status }
    {#case "draft"}
        <button class="primary">Submit for review</button>
    {#case "review"}
        <button class="secondary">Approve</button>
    {:else}
        <span class="text-muted">Already live</span>
{/switch}

The switch expression is evaluated once, then each {#case} value is compared against it with strict equality (===) in order; the first match renders and the rest are skipped. The {:else} branch is the default when nothing matches - it is optional and, when present, must be the last branch. If no case matches and there is no {:else}, the whole block renders nothing. Like {#if}, this works inside element content and attributes.

Both the switch expression and the case values may be any JavaScript expression - props.user?.role, "admin", props.record.status, or a {{ ... }} pre-computed variable. This pairs naturally with the pattern of computing one meaningful value in a {{ ... }} block and dispatching on it:

{{ const role = props.user?.role ?? "guest"; }}

{#switch role }
    {#case "admin"}
        <p>Admin controls visible.</p>
    {#case "editor"}
        <p>Editor mode - all features unlocked.</p>
    {:else}
        <a href="/login">Log in</a>
{/switch}

Content between the {#switch} and the first {#case} is dead code - it never renders, matching how JavaScript's own switch treats statements before the first case.

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. Use props.records.length > 0 if 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 0 is falsy. For numeric IDs starting from 0, check props.id != null rather than props.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 {{ ... }}.