Lit exports two special sentinel values, nothing and noChange, for precisely controlling what happens (or doesn't happen) at a given binding. This lesson explains the difference and when to use each.
Two Different Kinds of 'Do Nothing'
nothing tells Lit to render *nothing* at a given position — if something was previously rendered there, it's removed. It's the correct value to return from a conditional when you want an element, attribute, or text node to disappear entirely.
noChange is different and more advanced: it tells Lit to leave whatever was previously rendered at that position completely untouched, as if this update to that specific binding never happened. It's primarily used inside custom directives, not typical application code.
import { html, nothing } from 'lit';
render() {
return html`
<p class=${this.isError ? 'error' : nothing}>${this.message}</p>
`;
}
// When isError is false, the class attribute is removed entirely,
// not set to the string "nothing".
Using nothing for the class attribute removes it cleanly, rather than leaving behind an empty or literal "nothing" string.
Importing the Sentinels
import { html, nothing, noChange } from 'lit';
nothing works at any binding position: text content, attribute, boolean attribute, or property.
At an attribute-position binding, nothing removes the attribute entirely, similar to ?attr=${false}.
noChange is almost never used directly in component code — it exists mainly for directive authors.
Returning undefined from a text-position expression behaves like nothing for rendering purposes, but using nothing explicitly is clearer intent.
nothing vs noChange Cheat Sheet
Two sentinels that look similar but solve very different problems.
Sentinel
Meaning
Typical Use
nothing
Render/set nothing here; remove any previous content
Conditional rendering, removing an attribute
noChange
Leave the previous render output at this binding untouched
Custom directive implementations
undefined
Treated like nothing in text bindings
Acceptable but less explicit than nothing
'' (empty string)
Renders literally as an empty string, not the same as removing an attribute
Rarely what you actually want
nothing in Everyday Component Code
The most common use of nothing is as the 'else' branch of a ternary at any binding position, whether that's text content, a class attribute, or a boolean attribute — anywhere you want 'remove this' rather than 'set this to an empty value'.
Three separate uses of nothing: an optional class, an optional ARIA label, and an optional icon child template.
noChange for Custom Directive Authors
When writing a custom directive (covered in the directives lessons), your directive's update()/render() method can return noChange to signal 'I decided not to update the DOM this time' — for example, a directive that only updates when a value crosses some threshold, otherwise leaving the previously rendered value in place.
import { Directive, directive, noChange } from 'lit/directive.js';
class RoundedDirective extends Directive {
#last;
render(value) {
const rounded = Math.round(value);
if (rounded === this.#last) return noChange; // skip redundant DOM writes
this.#last = rounded;
return rounded;
}
}
This is an advanced pattern — most application-level components never need to return noChange directly.
Common Mistakes
Using an empty string '' instead of nothing when the intent is to remove an attribute entirely, leaving a stray empty attribute behind.
Confusing nothing (removes content) with noChange (leaves existing content as-is) — they solve opposite problems.
Reaching for noChange in regular component render() methods instead of in a custom directive, where it rarely makes sense.
Forgetting to import nothing from 'lit' and instead using the bare word nothing as an undefined variable.
Key Takeaways
nothing removes whatever was previously rendered at a binding position — text, attribute, or property.
noChange preserves the previous render output untouched, and is mainly relevant to custom directive authors.
Prefer nothing over an empty string when the goal is to remove an attribute or child, not set it to an empty value.
Both sentinels must be imported explicitly from the lit package.
Pro Tip
Whenever you write ? '' : ... or ? undefined : ... in a template as a way of hiding something, replace it with nothing — it makes the intent to 'remove this entirely' explicit both to readers and to Lit's rendering logic.
You now understand Lit's nothing and noChange sentinels. Next, dive into properties in more depth, including public reactive properties.