Almost no real interface always shows exactly the same content: a task can be pending, in progress, or done, and you probably want to show a different badge depending on its status. In the previous lesson you already used, in passing, a ternary operator inside an interpolation; in this lesson you'll go deeper into the different ways of rendering content conditionally in Lit, when each one is appropriate, and you'll apply them to give <task-card> a status badge that changes based on the task's data.

Contents

  1. Conditional rendering: it's not magic, it's JavaScript
  2. The ternary operator: the most common option
  3. The && operator: showing or hiding without an alternative
  4. Extracting logic into helper functions
  5. Watch out for "falsy" values when using &&
  6. The when directive: a passing mention
  7. Applying it to <task-card>: a badge based on status

  1. Conditional rendering: it's not magic, it's JavaScript

One of the most important ideas to internalize about Lit is that there is no special template syntax for conditionals, in the style of the {% if %} you may know from other template engines in other contexts. Lit doesn't need to invent its own syntax because, as you saw in the lesson "Lit's Template Engine", inside ${} you can write any valid JavaScript expression, and JavaScript already has operators capable of expressing a condition as an expression: the ternary operator and the && operator.

This is, in fact, one of the hallmarks of Lit's design: instead of creating a parallel templating language with its own rules to learn, it reuses JavaScript as-is. Anyone who already knows JavaScript already knows, without realizing it, a large part of "Lit's conditional syntax".

  1. The ternary operator: the most common option

The ternary operator (condition ? valueIfTrue : valueIfFalse) is the most common way to render conditionally in Lit when there are two possible alternatives, both with content to show:

render() {
  return html`
    <p>${this.completada ? 'Tarea finalizada' : 'Tarea pendiente'}</p>
  `;
}

The result of each branch of the ternary can be, just like in any interpolation, plain text, a number, or even another nested html template (as seen in the previous lesson):

render() {
  return html`
    <p>
      ${this.completada
        ? html`<span class="icono-ok">✓</span> Finalizada`
        : html`<span class="icono-pendiente">○</span> Pendiente`}
    </p>
  `;
}

Here each branch of the ternary returns a different html template, with its own <span> with a different class. Lit handles this without any problem: every time render() runs, it evaluates the condition and places the result of the corresponding branch in the DOM, replacing whatever was there before if the active branch has changed since the previous render.

  1. The && operator: showing or hiding without an alternative

When you only care about showing something if a condition holds, and there's no alternative content to show otherwise, the usual pattern is the logical && operator:

render() {
  return html`
    <article>
      <h3>${this.titulo}</h3>
      ${this.urgente && html`<p class="aviso">⚠ Esta tarea es urgente</p>`}
    </article>
  `;
}

This pattern relies on how JavaScript evaluates the && operator: if the left-hand operand (this.urgente) is "false", the whole expression evaluates to that false value, without even evaluating the right-hand operand; if it's "true", the whole expression takes the value of the right-hand operand (the warning's html template). When this.urgente is false, the whole interpolation is false, and Lit, upon finding false (or null, or undefined) as the result of a content interpolation, simply renders nothing at that point, leaving no trace in the DOM.

This pattern is preferable to the ternary when there's no reasonable alternative to show otherwise; writing this.urgente ? html\...` : ''works just as well, butthis.urgente && html`...`` communicates more clearly the intent of "show this only if the condition holds, and nothing otherwise".

  1. Extracting logic into helper functions

When conditional logic starts to have more than two or three branches, or when figuring out what to show is a bit more elaborate than a simple comparison, it's much more readable to extract that logic into a method of the class (a helper function) and call it from within the interpolation:

import { LitElement, html } from 'lit';

class TaskCard extends LitElement {
  constructor() {
    super();
    this.estado = 'en-progreso'; // 'pendiente' | 'en-progreso' | 'hecha'
  }

  renderIndicadorEstado() {
    if (this.estado === 'hecha') {
      return html`<span class="indicador ok">✓ Hecha</span>`;
    }
    if (this.estado === 'en-progreso') {
      return html`<span class="indicador progreso">◐ En progreso</span>`;
    }
    return html`<span class="indicador pendiente">○ Pendiente</span>`;
  }

  render() {
    return html`
      <article>
        <h3>${this.titulo}</h3>
        ${this.renderIndicadorEstado()}
      </article>
    `;
  }
}

This pattern —a renderSomething() method that the class itself calls from render()— is extremely common in real Lit code, including the official documentation and large projects. It lets you use full JavaScript statements (if, switch, loops, intermediate variables with const) that wouldn't fit directly inside a single ${}, since only expressions are allowed there, as explained in the previous lesson. The only requirement is that the method, just like render() itself, ends up returning a value valid for interpolation: an html template, text, a number, or null/undefined/false if nothing should be shown.

  1. Watch out for "falsy" values when using &&

The condition && html\...`pattern seen in section 3 hides a classic JavaScript trap worth knowing about. The&&operator doesn't strictly check whether the condition istrue; it checks whether it's "truthy" in JavaScript's broad sense, and there are several values JavaScript considers "falsy" besides false: 0, ''(empty string),null, undefinedandNaN`.

The problem shows up when the condition is a number that can legitimately be 0:

render() {
  return html`
    <article>
      <h3>${this.titulo}</h3>
      ${this.numeroDeComentarios && html`<p>${this.numeroDeComentarios} comentarios</p>`}
    </article>
  `;
}

If this.numeroDeComentarios is 0, the expression 0 && html\...`evaluates to0(not the template), and here's the trap: Lit does render the number0as text content, so a lone0` will literally appear on screen, instead of nothing at all. The usual fix is to force an explicit comparison that produces a real boolean:

${this.numeroDeComentarios > 0 && html`<p>${this.numeroDeComentarios} comentarios</p>`}

This general rule is worth remembering whenever && is used for conditional rendering: if the condition is a number or a string that could legitimately be 0 or '', it must be explicitly converted to a boolean (with a comparison like > 0, or with !!value) before using it with &&.

  1. The when directive: a passing mention

Among its catalog of built-in directives, Lit includes one called when that expresses the same idea as a ternary operator in a somewhat more explicit way:

import { when } from 'lit/directives/when.js';

render() {
  return html`
    ${when(
      this.completada,
      () => html`<span>✓ Finalizada</span>`,
      () => html`<span>○ Pendiente</span>`
    )}
  `;
}

when receives the condition, a function that produces the template for the true case, and optionally another function for the false case. Its behavior, in practice, is very similar to a ternary operator with nested templates; the main difference is that the branches are passed as functions (which delays their execution until they're actually needed) and that, for anyone reading the code, it makes the intent of "conditional rendering" as a concept more explicit, as opposed to a ternary that could also be selecting a simple value unrelated to templates.

This directive is mentioned here only so you recognize it if you find it in the official documentation or in third-party projects; the study of Lit's directives in general —what they are, how they work internally, and when they add value over plain JavaScript— is the content of module 7, "Directives and Advanced Template Features". For the rest of this course, and particularly in this module, standard JavaScript (ternaries, &&, helper functions) will keep being used as the main technique, precisely because it requires no additional imports and demonstrates that conditional rendering in Lit doesn't depend on any special feature.

  1. Applying it to <task-card>: a badge based on status

With the previous techniques, you can now give <task-card> a status badge that changes its appearance and text depending on whether the task is pending, in progress, or done. The helper-function pattern from section 4 will be used, since it's the most readable option when there are more than two alternatives:

import { LitElement, html } from 'lit';

class TaskCard extends LitElement {
  constructor() {
    super();
    this.titulo = 'Preparar la demo del sprint';
    this.estado = 'en-progreso'; // 'pendiente' | 'en-progreso' | 'hecha'
    this.urgente = false;
  }

  renderInsigniaEstado() {
    if (this.estado === 'hecha') {
      return html`<span class="insignia insignia--hecha">✓ Hecha</span>`;
    }
    if (this.estado === 'en-progreso') {
      return html`<span class="insignia insignia--progreso">◐ En progreso</span>`;
    }
    return html`<span class="insignia insignia--pendiente">○ Pendiente</span>`;
  }

  render() {
    return html`
      <article>
        <h3>${this.titulo}</h3>
        ${this.renderInsigniaEstado()}
        ${this.urgente && html`<p class="aviso">⚠ Urgente</p>`}
      </article>
    `;
  }
}

customElements.define('task-card', TaskCard);

This component combines the three techniques seen in the lesson: a helper function (renderInsigniaEstado) that encapsulates a three-branch logic using regular if statements (impossible to express directly inside a single ${}), and the && pattern to show the urgency warning only when appropriate. Note that the CSS classes (insignia--hecha, insignia--progreso...) are interpolated here only as name references; their actual visual definition —what those colors and icons look like— is content for module 4, "Styling Lit Components". For now, without those styles, the badge will look like plain unstyled text, and that's exactly what's expected at this point in the course.

Common Mistakes and Tips

  • Using && with numeric conditions without converting to boolean: as seen in section 5, cantidad && html\...`can show a lone0on screen ifcantidad is zero. It's always worth forcing an explicit comparison (cantidad > 0`) in these cases.
  • Nesting too many ternaries in a row: chaining several ternaries (a ? x : b ? y : c ? z : w) to represent more than two alternatives is hard to read. As soon as there are three or more branches, it's preferable to extract the logic into a helper function with if/switch, as done in section 7.
  • Forgetting that returning an empty string '' in a branch is not the same as rendering nothing: even though the visual result is the same (no text appears), returning '' still inserts an empty text node into the DOM, while returning null, undefined or false makes Lit insert nothing at all. In practice this difference rarely matters, but it's worth knowing so you're not surprised when inspecting the DOM.
  • Repeating the same condition several times in the template: if you need to check this.estado === 'hecha' in several places of a long template, that's a sign you should extract that logic into a helper function or a local variable computed at the start of render(), instead of repeating the comparison.

Exercises

  1. Add a fourth possible status to the renderInsigniaEstado() function, 'bloqueada', that shows a badge with the text "⛔ Bloqueada" and the class insignia--bloqueada.
  2. Using the && pattern with the fix from section 5, add a field this.numeroDeComentarios to <task-card> (initialize it to 0) and show a paragraph "N comentarios" only when that number is greater than zero. Manually verify, by changing the value in the constructor, that with 0 no lone "0" appears on screen.
  3. Rewrite the status badge from section 7 using the when directive mentioned in section 6, checking its exact signature in Lit's official documentation (lit/directives/when.js), purely as documentation-reading practice (there's no need to integrate it into the rest of the course).

Solutions

renderInsigniaEstado() {
  if (this.estado === 'hecha') {
    return html`<span class="insignia insignia--hecha">✓ Hecha</span>`;
  }
  if (this.estado === 'en-progreso') {
    return html`<span class="insignia insignia--progreso">◐ En progreso</span>`;
  }
  if (this.estado === 'bloqueada') {
    return html`<span class="insignia insignia--bloqueada">⛔ Bloqueada</span>`;
  }
  return html`<span class="insignia insignia--pendiente">○ Pendiente</span>`;
}
constructor() {
  super();
  // ...
  this.numeroDeComentarios = 0;
}

render() {
  return html`
    <article>
      <h3>${this.titulo}</h3>
      ${this.renderInsigniaEstado()}
      ${this.numeroDeComentarios > 0
        ? html`<p>${this.numeroDeComentarios} comentarios</p>`
        : ''}
    </article>
  `;
}

With this.numeroDeComentarios at 0, the condition this.numeroDeComentarios > 0 is false, so no paragraph is shown and no lone "0" appears.

import { when } from 'lit/directives/when.js';

render() {
  return html`
    <article>
      <h3>${this.titulo}</h3>
      ${when(
        this.estado === 'hecha',
        () => html`<span class="insignia insignia--hecha">✓ Hecha</span>`,
        () => html`<span class="insignia insignia--pendiente">○ Pendiente / en progreso</span>`
      )}
    </article>
  `;
}

This solution simplifies things to just two branches for practice purposes; an exact replica of the four branches from section 1 would require nesting several calls to when, which illustrates precisely why, with more than two alternatives, a helper function with if/switch is usually preferred over nesting directives.

Conclusion

In this lesson you saw that conditional rendering in Lit requires no special syntax: it relies directly on standard JavaScript operators, mainly the ternary operator for choosing between two alternatives and the && operator for showing or hiding content without an alternative, taking care to explicitly convert to boolean any conditions that could be 0 or an empty string. You also learned to extract more complex conditional logic into helper functions within the class itself, and you got a passing look at the when directive, whose full study will come in module 7. With all this, <task-card> now shows a different status badge depending on whether the task is pending, in progress, or done, plus a conditional urgency warning.

In the next lesson, "List Rendering", you'll take a further step: instead of showing a single task, you'll learn to iterate over a whole array of tasks with Array.map inside a template, and you'll use that technique to build <task-list>, TaskFlow's first component capable of rendering several <task-card> cards from a collection of data.

Lit Course

Module 1: Introduction to Lit and Web Components

Module 2: Reactive Templates and Rendering

Module 3: Reactive Properties and State

Module 4: Styling Lit Components

Module 5: Events and Component Communication

Module 6: Lifecycle and Advanced Behavior

Module 7: Directives and Advanced Template Features

Module 8: Integration, Interoperability and Deployment

Module 9: Testing and Best Practices

Module 10: Project: Building TaskFlow

© Copyright 2026. All rights reserved