Understanding the Conditional Attribute Issue in Lit

If you are transitioning to Lit (or lit-html) from traditional templating engines like EJS, Handlebars, or vanilla string concatenation, you might hit a frustrating roadblock when trying to conditionally add boolean attributes like checked, disabled, or hidden.

You may attempt to write something like this:

// ❌ This will NOT work in Lit
return html`<input type="radio" name="LogType" value="${Constants.LogTypes.Member}" ${filters.logType === Constants.LogTypes.Member ? 'checked' : ''}>`;

When rendered, Lit completely ignores or strips out the conditional checked string. Why does this happen?

Why String Interpolation Fails in Lit Template Tags

Unlike standard templating engines that treat templates as plain text before sending them to an innerHTML parser, Lit leverages browser-native HTML <template> elements. Lit breaks down your template into static HTML structure and dynamic expressions before any code runs.

Because Lit relies on static HTML structure, you cannot inject arbitrary attribute names or string fragments directly into an HTML tag opening. Lit needs to explicitly know whether you are binding a standard HTML attribute, a boolean attribute, a DOM property, or an event listener.

The Fix: Use Lit's Boolean Attribute Syntax (?checked)

Lit provides a clean and declarative syntax for boolean attributes: the ? modifier prefix. When you place ? in front of an attribute name, Lit evaluates your expression as a boolean value:

  • If the expression is truthy, Lit adds the attribute (e.g., checked).
  • If the expression is falsy, Lit completely removes the attribute from the DOM.

Here is how to update your code using the boolean attribute binding:

import { html } from 'lit';

return html`
  <input 
    type="radio" 
    name="LogType" 
    class="form__control form__control--radio" 
    value="${Constants.LogTypes.Member}"
    ?checked="${filters.logType === Constants.LogTypes.Member}"
  >
`;

Alternative: Use Property Binding (.checked)

When dealing with standard HTML form controls (like <input> elements), there is an important distinction between HTML attributes and DOM properties. The initial checked attribute sets the default state, but user interaction updates the element's checked DOM property directly.

If you want to ensure the UI updates reliably even after user interactions, you can bind directly to the DOM property using the . prefix:

return html`
  <input 
    type="radio" 
    name="LogType" 
    class="form__control form__control--radio" 
    value="${Constants.LogTypes.Member}"
    .checked="${filters.logType === Constants.LogTypes.Member}"
  >
`;

Summary of Lit Binding Prefixes

To avoid similar templating bugs in the future, remember this quick reference for Lit binding syntaxes:

  • attribute="${value}" – Sets a standard HTML attribute string.
  • ?attribute="${boolean}" – Toggles a boolean attribute on or off based on truthiness.
  • .property="${value}" – Binds directly to a JavaScript DOM property.
  • @event="${handler}" – Attaches an event listener to the element.