# Conditional spintax: {?VAR?then|else} Sometimes the choice between two phrasings is not a coin flip — it depends on a fact. The product has a free tier, or it doesn't. The merchant accepts cryptocurrency, or only fiat. Conditional spintax is the value-driven counterpart to `{a|b}` enum branches: instead of picking randomly, it picks based on whether a variable is truthy. ## The three forms Conditional syntax adds three tokens on top of the core spintax language. All three are evaluated **before** enums and permutations resolve — they are a pre-pass, not a runtime branch. | Form | Meaning | | --- | --- | | `{?VAR?then}` | Render `then` if `%VAR%` is truthy; render nothing otherwise. | | `{?VAR?then|else}` | Render `then` if truthy; render `else` if falsy. | | `{?!VAR?then[|else]}` | Inverted: render `then` when `%VAR%` is _falsy_. | The `!` prefix flips the check. There is no separate `{?VAR??else}` form — if you only want the falsy branch, write `{?!VAR?else}`. **Variable names follow the `%var%` rules.** They match `[A-Za-z_][A-Za-z0-9_]*` and are case-insensitive — `{?HasFreeTier?…}` and `{?hasfreetier?…}` read the same value. ## Truthy and falsy The truthy rule is deliberately simpler than JavaScript's. You should not have to remember coercion edge cases. | Value of `%VAR%` | Truthy? | | --- | --- | | not declared at all | falsy | | empty string `""` | falsy | | whitespace only (spaces, tabs, newlines) | falsy | | `"0"` | **truthy** (string zero is non-empty) | | `"false"` | **truthy** (still a non-empty string) | | `"x"`, `"
…
"`, raw spintax | truthy | That is the whole rule. **Truthy = at least one non-whitespace character.** If you ever need a JavaScript-flavoured truthiness, do that check in the assembler before passing the variable in. ### Truthiness reads the raw value The lookup is on the raw stored value of the variable. Nested `%var%` references inside that value are _not_ expanded for the truthy check — only later, when `expandVariables` runs as a separate stage. ``` #set %X% = %Other% {?X?yes|no} → "yes" ``` Even if `%Other%` would expand to an empty string, `%X%`'s raw value is the literal string `%Other%` — which is non-empty, hence truthy. To get value-aware truthiness, write `%X%` as a clean `'1'` or `''` directly. Compute the guard in the assembler before passing it in. ## The two-pass pipeline The engine processes a template in stages. Conditionals get **two evaluation passes**: ``` 1. strip comments 2. extract #set / #def directives 3. merge variables 4. apply conditionals ← pass 1 5. expand %var% references 6. apply conditionals ← pass 2 7. resolve enumerations 8. resolve permutations 9. post-process ``` **Pass 1** handles conditionals authored directly in the template body. It runs before variable expansion so a falsy branch is discarded without wasting cycles on its `%var%`s. **Pass 2** handles the case where a variable's value _itself_ contains a conditional: ``` #set %CTA% = {?HasBonus?Claim bonus|Deposit now} %CTA% /# pass 2 sees the conditional after expansion #/ ``` One clarification: pass 2 does not loop. If the chosen branch contains a fresh `%var%` reference, that reference is left literal — there is no third expansion pass. For most real templates this is fine; the guards used in conditional branches (`%HasCrypto%`, `%HasFiat%`) are typically the same ones already resolved in pass 1. ## Worked example: payment-methods section This is the case the syntax was designed for. A product page renders a "Payment methods" section. Some merchants accept crypto, some accept only fiat, some accept both, a few have no payment integration configured yet. The variable assembler computes two guards plus three pre-rendered HTML chunks: ``` %HasCrypto% "1" or "" %HasFiat% "1" or "" %CryptoSection% /# already-renderedPayment details will be published shortly.
}} ``` Read the bottom line as: _if not crypto and not fiat, render the fallback paragraph_. Nested conditionals short-circuit outer-first, so when `%HasCrypto% = "1"` the outer falsy check fails immediately and the inner conditional is never evaluated. Compare with the old workaround — a guard variable filled by spintax that picks between an empty string and a fallback paragraph at random with weighted probability. That worked, but the rendered output was non-deterministic and the template had to thread an extra variable through. Conditional syntax says exactly what it means. ## Pick vs conditional — do not confuse them ### Enum branches are uniform random. Conditionals are value-driven. The two look almost identical and behave completely differently: - `{Yes|No}` — the engine picks "Yes" or "No" with 50/50 probability. The choice **does not depend on any variable**. - `{?HasBonus?Yes|No}` — the engine renders "Yes" if `%HasBonus%` is truthy, "No" if not. The choice **depends entirely on the variable's value**. If you find yourself reaching for `{a|b}` hoping the engine will somehow notice a variable, stop and use `{?VAR?a|b}` instead. ## Anti-patterns ### 1\. Boolean operators There is no `&&`, `||`, `!=`, or `==` in conditional spintax. The syntax is intentionally minimal. If you need composite logic, compute the boolean in the variable assembler: ``` /# wrong: not supported #/ {?HasCrypto && HasLicense?…} /# right: compose in the assembler #/ #set %ShowCryptoBlock% = {?HasCrypto?{?HasLicense?1}} {?ShowCryptoBlock?…} ``` The assembler can use any host language and any logic. Spintax stays a templating tool, not a programming language. ### 2\. `#set` inside a branch `#set` directives are extracted _before_ any conditional pass — that is step 2 of the pipeline. A `#set` line inside a `{?…?…}` branch fires unconditionally; the conditional only controls whether the empty leftover of the line stays in the chosen branch's text. ``` /# wrong: both #set lines fire — the second wins #/ {?A? #set %x% = first |}{?A? #set %x% = second |}%x% → always "second", regardless of A ``` If you need conditional assignment, do it in the assembler. ### 3\. Top-level `|` as literal in `then` The first depth-0 `|` inside the body separates `then` from `else`. Any further `|` at depth 0 stays literal — but in the _else_ branch, not the _then_ branch. ``` {?A?x|y|z} /# A truthy → "x"; A falsy → "y|z" #/ ``` If the `then` branch needs a literal `|`, wrap it in nested braces or use the HTML entity `|`: ``` {?A?{x|y}} /# inner | is depth 1, not a separator #/ {?A?x | y} /# explicit entity, renders as "x | y" #/ ``` ### 4\. Confusing pre-pass and runtime Conditionals run as a pre-pass before enums and permutations resolve. That means a falsy branch is **completely discarded** — no random choice from it ever fires. If your template has a `%RandomQuirk%` permutation deep inside the falsy branch, that permutation is never evaluated when the conditional is falsy. Good. That is the point. ## Lenient parsing — malformed forms are not fatal Bare `?` in prose ("How? Like this?") is common. The parser is deliberately lenient: any `{?…` that does not match the grammar is left literal rather than throwing. The validator on this site (and the playground) does flag **balanced-malformed** shapes as warnings so you catch them in the editor: | Form | Treatment | | --- | --- | | `{?VAR?then` — no closing `}` | warning: unmatched open brace (same as any other `{`) | | `{??yes}` — empty name | warning: malformed conditional (empty name) | | `{?VAR}` — missing `?` separator | warning: malformed conditional (missing separator) | | `How? Like this?` | plain prose, no warning | At runtime, every malformed form is non-throwing. The engine continues into the normal enum/permutation passes; balanced malformed tokens may end up consumed by later stages, so do not rely on literal preservation. ## Quick checklist - Use `{?VAR?…}` when the choice depends on a value, never `{a|b}`. - Use `{?!VAR?…}` instead of inventing a separate "no" guard variable. - Compute composite booleans in the assembler. Spintax conditionals are atomic. - Never put `#set` inside a branch — it fires unconditionally. - If `then` needs a literal `|`, wrap it in `{…}` or use `|`. - Truthy = non-whitespace, full stop. Pre-compute special truthiness in the assembler. - Pre-render HTML fragments into variables for sections that change shape per tenant. Conditionals gate them. ## Try it live The [playground](https://spintax.net/play/) ships with a `{?HasFreeTier?…|…}` example in the default template. Toggle `%HasFreeTier%` between `1` and empty to see both branches without typing anything else. --- ## Continue the series - [Reverse authoring mindset](https://spintax.net/docs/authoring-mindset.md): Write the text first. Add markup last. - [Variables & multi-site reuse](https://spintax.net/docs/variables.md): Precedence, reroll gotchas, separator collisions. - [Permutations in practice](https://spintax.net/docs/permutations.md): Lists, title-style headings, separator rules. - [Grammar-safe synonymization](https://spintax.net/docs/grammar-safe-spintax.md): Binding rules. English agreement. Russian cases: families, prepositions, gender. - [Template composition](https://spintax.net/docs/template-composition.md): Variables as rendered HTML chunks. Item → section → orchestrator pipeline. - [Plural agreement](https://spintax.net/docs/plural-spintax.md): Pick the right noun form by count. RU/UK/BE and SR/HR/BS 3-form, EN-style 2-form. - [Repairing spintax templates](https://spintax.net/docs/repairing-spintax.md): Detect → one named recipe → re-check. Lint codes, the catalogue, the repair prompt. - [Spintax by example](https://spintax.net/examples.md): Every technique in the series, applied to one real paragraph, with real renders. [Open in playground](https://spintax.net/play/) [Back to all guides](https://spintax.net/docs.md) --- Source: https://spintax.net/docs/conditional-spintax/