Render different content based on conditions in Lit templates using ternaries, when(), choose(), ifDefined(), and cache().
Because Lit templates use plain JavaScript expressions, all standard JavaScript control flow is available. This page covers the most common patterns for conditional rendering.
The when() helper is a convenience wrapper around a ternary that makes single-branch conditionals easier to read.Import:lit/directives/when.jsSignature:
function when<C, T, F = undefined>( condition: C, trueCase: (c: C) => T, falseCase?: (c: C) => F): C extends Falsy ? F : T
When condition is truthy, when() calls trueCase(condition) and returns the result. When falsy, it calls falseCase(condition) if provided, otherwise returns undefined.
The condition value is passed to trueCase and falseCase, so TypeScript can
narrow its type inside each callback. This is especially useful when
condition is an object that may be undefined.
The choose() helper selects a template from a list of cases by matching a value with strict equality (===). It behaves like a switch statement without fallthrough.Import:lit/directives/choose.jsSignature:
const choose = <T, V, K extends T = T>( value: T, cases: Array<[K, () => V]>, defaultCase?: () => V) => V | undefined
ifDefined() is used in attribute bindings to set the attribute when the value is defined and remove the attribute when the value is undefined.Import:lit/directives/if-defined.jsSignature:
const ifDefined = <T>(value: T) => T | typeof nothing
import {html} from 'lit';import {ifDefined} from 'lit/directives/if-defined.js';// href attribute is only added when url is not undefinedhtml`<a href="${ifDefined(this.url)}">Link</a>`
// ariaLabel is removed when label is undefinedhtml`<button aria-label="${ifDefined(this.ariaLabel)}">Click</button>`
ifDefined() only removes the attribute for undefined. For null values,
the attribute remains (set to the string "null"). If you want to remove the
attribute for both null and undefined, use
ifDefined(value ?? undefined) or simply use nothing directly in the
attribute binding.
By default, switching between two different templates causes Lit to tear down the old DOM and create new DOM. cache() avoids this by keeping the DOM for each template alive in a hidden fragment, then swapping it back in when the template is rendered again.Import:lit/directives/cache.jsSignature:
import {html} from 'lit';import {cache} from 'lit/directives/cache.js';render() { return html` ${cache( this.isChecked ? html`<p>Input is checked</p>` : html`<p>Input is not checked</p>` )} `;}
cache() is most useful when:
Switching between two expensive-to-create subtrees.
A subtree holds internal state (scroll position, input text, focus) that you want to preserve across switches.
cache() increases memory usage because it keeps inactive DOM alive. Only
use it when the performance gain from skipping DOM creation outweighs the
extra memory cost.