SfSkills Domains Docs GitHub

Home / lwc / lwc-conditional-rendering

lwc-conditional-rendering

Use when writing, reviewing, or migrating Lightning Web Component templates that branch between UI states with `lwc:if`, `lwc:elseif`, and `lwc:else`, including getter-backed booleans, loading/error/ready state machines, keep-state vs reset-state toggles, and legacy `if:true` / `if:false` cleanup. Triggers: 'lwc:if vs if:true', 'lwc:elseif not working', 'conditional rendering in lwc', 'complex boolean in template'. NOT for choosing between component types at runtime — use lwc/lwc-dynamic-components. NOT for list rendering — use `for:each` or `iterator:it`.

Open the skill folder on GitHub


LWC Conditional Rendering

Use this skill when a Lightning Web Component template must branch between UI states — loading vs ready, permitted vs denied, empty vs list, step 1 vs step 2 — or when a legacy template still uses if:true / if:false and needs to be migrated to the modern lwc:if / lwc:elseif / lwc:else trio. It activates on questions about evaluation rules, getter-backed booleans, lifecycle interactions with renderedCallback and lwc:ref, and the idiom for complex boolean logic.


Before Starting

Gather this context before writing or migrating conditional markup:


Core Concepts

Modern conditional rendering in LWC uses three cooperating directives, getters for anything that is not a single boolean, and a mount/unmount model — not a hide/show model.

The Directive Trio: lwc:if, lwc:elseif, lwc:else

lwc:if={prop} evaluates the expression and, when the result is truthy, renders the element and its subtree. lwc:elseif={other} must immediately follow a sibling lwc:if or another lwc:elseif and is evaluated only when the preceding branch was falsy. lwc:else has no expression — lwc:else={foo} is a parse-time error — and it matches whatever remains. The expression inside lwc:if / lwc:elseif is not a reactive watch: reactivity comes from the underlying property (or getter dependency), not from the directive itself. The directive simply re-evaluates on the next rerender.

Getters Are The Idiom For Computed Booleans

Template expressions in LWC are intentionally limited. You cannot write lwc:if={a && b}, lwc:if={status !== 'error'}, or lwc:if={items.length > 0} unless the component's apiVersion is 66.0 or later and you accept Beta risk (see below). Default practice: put computed logic in a JavaScript getter and reference the getter: get isReady() { return this.status === 'done' && !this.error; }, then lwc:if={isReady}. Getters compose cleanly, are unit-testable, and keep the template readable. Inverting a condition should use lwc:else rather than a negated getter when the negation exists only to flip a single branch.

Complex Template Expressions (Spring '26 Beta, apiVersion 66.0+)

Beginning in Spring '26, components at apiVersion 66.0+ may use complex JavaScript expressions directly in HTML templates — e.g. lwc:if={a && b} without a getter. Salesforce documents this as Beta with an explicit "Do not use complex template expressions in production." warning. Prefer getters for production components; use inline expressions only for pilots where the Beta tradeoff is accepted and documented.

Branches Mount And Unmount — They Do Not Hide

When lwc:if flips from true to false, the DOM subtree is removed — not hidden. Child component instances are destroyed, internal state is lost, disconnectedCallback fires, and lwc:ref to anything inside that subtree becomes undefined. When the branch flips back, a fresh instance is created and renderedCallback fires again, so any side-effect code there must be idempotent. If the UX needs to hide a panel while preserving its state (open filter drawer, partially filled form), use CSS display:none or a class toggle instead. Branches also form their own sub-trees for slot assignment: a slotted element lives inside exactly one branch at a time.

Legacy if:true / if:false Still Work — But Are Discouraged

if:true={prop} and if:false={prop} are the pre-lwc:if API. Salesforce's current guidance says they are no longer recommended, may be removed in the future, and are less performant in chained conditions because they do not share the lwc:if / lwc:elseif short-circuit. Migrating is straightforward and the skill's checker flags every instance.


Common Patterns

Loading / Error / Ready State Machine

When to use: A component fetches data and needs to show a spinner, an error card, or the ready view — exactly one at a time.

How it works: Expose a status property ('loading' | 'error' | 'ready'). Back each state with a getter (isLoading, isError, isReady). In the template chain lwc:if={isLoading} → lwc:elseif={isError} → lwc:else for the ready branch. The lwc:else block has no expression.

Why not the alternative: Three parallel lwc:if blocks rely on the JS to guarantee mutual exclusion, so a bug can render two branches at once. The chained directives make the exclusivity a template-level invariant.

Keep-State Toggle vs Reset-State Toggle

When to use: A panel or drawer opens and closes via a button. Whether to use lwc:if or a CSS class depends on whether the user expects their partial state to survive.

How it works: For "reset every time" (confirmation modals, wizards that restart), use lwc:if={isOpen} — the subtree is fresh on every open. For "preserve my input" (filter drawers, collapsed sections), keep the component mounted and toggle display:none via a computed class getter. Document the choice in a comment so future edits do not regress it.

Why not the alternative: Blindly using lwc:if for a drawer destroys in-progress user input; blindly using CSS hide means stale state leaks between opens.


Decision Guidance

SituationRecommended ApproachReason
Simple show/hide of one elementlwc:if={flag}Simplest primitive; re-mount is usually desirable
Two mutually exclusive brancheslwc:if + lwc:elseEncodes the exclusivity in the template
Three or more mutually exclusive brancheslwc:if + lwc:elseif + lwc:elseShort-circuits and avoids parallel lwc:if bugs
Boolean depends on multiple propertiesGetter that returns a booleanTemplate expressions are intentionally limited
Pilot-only inline boolean (a && b) in templateComplex template expression at apiVersion 66.0+Beta — Salesforce warns against production use; getters remain the safe default
Panel must preserve in-progress state across togglesCSS display:none via class getterlwc:if re-mounts and loses child state
Existing template uses chained if:true / if:falseMigrate to lwc:if / lwc:elseif / lwc:elseLegacy path is slower and officially discouraged

  1. Inventory the branches — list every UI state the component can show, confirm mutual exclusivity, and map each state to a property or getter.
  2. Decide mount vs hide — for each toggle, ask whether child state must survive. Pick lwc:if for reset, CSS hide for preserve.
  3. Move logic into getters — any boolean that is not already a single property becomes a getter with a descriptive name (isReady, canEdit).
  4. Write the chain — use lwc:if + lwc:elseif + lwc:else for exclusive branches; use standalone lwc:if for independent toggles.
  5. Check lifecycle assumptions — verify renderedCallback inside branches is idempotent and that no connectedCallback reaches into a lwc:ref that may not exist.
  6. Run the checker — python3 scripts/check_lwc_conditional_rendering.py --lwc-dir force-app/main/default/lwc flags legacy directives, orphan lwc:elseif, expressions in lwc:if, and lwc:else={...} errors.
  7. Migrate legacy — replace every flagged if:true / if:false with the modern trio in the same PR so the checker stays green.

Review Checklist


Salesforce-Specific Gotchas

  1. lwc:if unmounts — it does not hide — Flipping to false destroys the subtree, fires disconnectedCallback, and voids any lwc:ref pointing inside. A user's partially filled input is lost unless the parent lifts the state up.
  2. renderedCallback fires again on re-entry — Every time the branch flips back to true, a fresh instance is created and renderedCallback runs again. Side-effect code (third-party libs, focus(), one-time measurement) must be guarded for idempotency.
  3. lwc:else={foo} is a parse-time error — lwc:else takes no expression. Authors migrating from other frameworks frequently try to add one.
  4. lwc:elseif must be a sibling following lwc:if — Wrapping the lwc:if in a <div> and putting lwc:elseif outside that <div> breaks the chain; the lwc:elseif becomes orphaned and throws at compile time.
  5. Complex expressions inside lwc:if do not compile — lwc:if={a && b} is not valid. Put the logic in a getter. This is intentional — templates stay declarative and testable.
  6. Legacy if:true / if:false are on a removal path — They still compile, but Salesforce's own docs call them "no longer recommended" and flag them as less performant in chained conditions.

Output Artifacts

ArtifactDescription
Updated templatelwc:if / lwc:elseif / lwc:else chain with getter-backed booleans
Migration notesBefore/after snippets converting if:true / if:false to the modern trio
Checker reportFile-and-line findings for legacy directives, orphan lwc:elseif, complex expressions, and lwc:else={...} errors

Package files