or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-components.mdcustom-directives.mddom-query-decorators.mdindex.mdproperty-decorators.mdstatic-templates.mdtemplate-directives.md
tile.json

template-directives.mddocs/

Template Directives

Built-in directives for advanced template functionality including conditional rendering, loops, styling, async operations, and performance optimization.

Capabilities

Conditional Rendering

Directives for conditionally showing/hiding content based on conditions.

When Directive

/**
 * Conditionally renders content based on a boolean condition
 * Supports optional false case template
 */
function when<T, F>(
  condition: boolean,
  trueCase: () => T,
  falseCase?: () => F
): T | F | typeof nothing;

Usage Examples:

import { LitElement, html } from "lit";
import { when } from "lit/directives/when.js";

class ConditionalComponent extends LitElement {
  @property({ type: Boolean }) isLoggedIn = false;
  @property() user = { name: "", role: "" };
  
  render() {
    return html`
      ${when(
        this.isLoggedIn,
        () => html`<p>Welcome, ${this.user.name}!</p>`,
        () => html`<p>Please log in</p>`
      )}
      
      ${when(
        this.user.role === "admin",
        () => html`<button>Admin Panel</button>`
      )}
    `;
  }
}

If Defined Directive

/**
 * Renders value only if it's defined (not null or undefined)
 * Useful for optional attributes
 */
function ifDefined(value: unknown): unknown | typeof nothing;

Usage Examples:

import { ifDefined } from "lit/directives/if-defined.js";

class OptionalAttributes extends LitElement {
  @property() title?: string;
  @property() href?: string;
  
  render() {
    return html`
      <a 
        href=${ifDefined(this.href)}
        title=${ifDefined(this.title)}
      >
        Link text
      </a>
    `;
  }
}

Choose Directive

/**
 * Renders one of multiple templates based on a key
 * Efficient for switch-like conditional rendering
 */
function choose<T>(
  value: T,
  cases: Array<[T, () => unknown]>,
  defaultCase?: () => unknown
): unknown;

Collection Rendering

Directives for rendering collections of data with keys and transformations.

Repeat Directive

/**
 * Efficiently renders a list of items with optional key function
 * Provides optimal DOM diffing for list updates
 */
function repeat<T>(
  items: Iterable<T>,
  keyFn: KeyFn<T>,
  template: ItemTemplate<T>
): IterableIterator<TemplateResult>;

function repeat<T>(
  items: Iterable<T>,
  template: ItemTemplate<T>
): IterableIterator<TemplateResult>;

/** Key extraction function type */
type KeyFn<T> = (item: T, index: number) => unknown;

/** Item template function type */
type ItemTemplate<T> = (item: T, index: number) => unknown;

Usage Examples:

import { repeat } from "lit/directives/repeat.js";

class ListComponent extends LitElement {
  @property() items = [
    { id: 1, name: "Apple", price: 1.50 },
    { id: 2, name: "Banana", price: 0.75 },
    { id: 3, name: "Orange", price: 2.00 }
  ];
  
  render() {
    return html`
      <ul>
        ${repeat(
          this.items,
          (item) => item.id, // Key function
          (item, index) => html`
            <li data-index=${index}>
              ${item.name} - $${item.price}
              <button @click=${() => this._removeItem(item.id)}>Remove</button>
            </li>
          `
        )}
      </ul>
    `;
  }
  
  private _removeItem(id: number) {
    this.items = this.items.filter(item => item.id !== id);
  }
}

Map Directive

/**
 * Maps over iterable values with a template function
 * Simpler alternative to repeat when keys aren't needed
 */
function map<T>(
  items: Iterable<T>,
  f: (value: T, index: number) => unknown
): IterableIterator<unknown>;

Usage Examples:

import { map } from "lit/directives/map.js";

class SimpleList extends LitElement {
  @property() colors = ["red", "green", "blue"];
  
  render() {
    return html`
      <div class="color-list">
        ${map(this.colors, (color, index) => html`
          <div 
            class="color-swatch" 
            style="background-color: ${color}"
            title="Color ${index + 1}: ${color}"
          ></div>
        `)}
      </div>
    `;
  }
}

Join Directive

/**
 * Joins iterable values with a separator
 * Multiple overloads for different use cases
 */
function join<I, S>(
  items: Iterable<I>,
  joiner: S
): IterableIterator<I | S>;

function join<I, S>(
  items: Iterable<I>,
  joiner: (index: number) => S
): IterableIterator<I | S>;

Usage Examples:

import { join } from "lit/directives/join.js";

class TagList extends LitElement {
  @property() tags = ["javascript", "web-components", "lit"];
  
  render() {
    return html`
      <p>Tags: ${join(this.tags, ", ")}</p>
      
      <nav>
        ${join(
          this.tags.map(tag => html`<a href="/tags/${tag}">${tag}</a>`),
          html` | `
        )}
      </nav>
    `;
  }
}

Styling Directives

Directives for dynamic CSS class and style management.

Class Map Directive

/**
 * Applies CSS classes based on object properties
 * True values add the class, false/falsy values remove it
 */
function classMap(classInfo: ClassInfo): DirectiveResult;

/** Class information object type */
interface ClassInfo {
  [name: string]: string | boolean | number;
}

Usage Examples:

import { classMap } from "lit/directives/class-map.js";

class StyledButton extends LitElement {
  @property({ type: Boolean, reflect: true }) primary = false;
  @property({ type: Boolean, reflect: true }) disabled = false;
  @property() size = "medium";
  
  render() {
    const classes = {
      "btn": true,
      "btn--primary": this.primary,
      "btn--disabled": this.disabled,
      [`btn--${this.size}`]: true
    };
    
    return html`
      <button class=${classMap(classes)} ?disabled=${this.disabled}>
        <slot></slot>
      </button>
    `;
  }
}

Style Map Directive

/**
 * Applies CSS styles based on object properties
 * Handles vendor prefixes and CSS custom properties
 */
function styleMap(styleInfo: StyleInfo): DirectiveResult;

/** Style information object type */
interface StyleInfo {
  [name: string]: string | number | undefined | null;
}

Usage Examples:

import { styleMap } from "lit/directives/style-map.js";

class DynamicStyles extends LitElement {
  @property() backgroundColor = "#ffffff";
  @property({ type: Number }) width = 200;
  @property({ type: Number }) opacity = 1;
  
  render() {
    const styles = {
      backgroundColor: this.backgroundColor,
      width: `${this.width}px`,
      opacity: this.opacity,
      "--custom-property": this.backgroundColor
    };
    
    return html`
      <div style=${styleMap(styles)}>
        Dynamic styled content
      </div>
    `;
  }
}

Performance Optimization

Directives for optimizing template rendering performance.

Cache Directive

/**
 * Caches template instances based on template identity
 * Prevents recreation of DOM when switching between templates
 */
function cache(value: TemplateResult): DirectiveResult;

Usage Examples:

import { cache } from "lit/directives/cache.js";

class ViewSwitcher extends LitElement {
  @property() currentView = "list";
  
  private _renderListView() {
    return html`<div class="list-view">List content...</div>`;
  }
  
  private _renderGridView() {
    return html`<div class="grid-view">Grid content...</div>`;
  }
  
  render() {
    return html`
      <nav>
        <button @click=${() => this.currentView = "list"}>List</button>
        <button @click=${() => this.currentView = "grid"}>Grid</button>
      </nav>
      
      <main>
        ${cache(
          this.currentView === "list" 
            ? this._renderListView()
            : this._renderGridView()
        )}
      </main>
    `;
  }
}

Guard Directive

/**
 * Guards against expensive template updates
 * Only re-renders when dependencies change
 */
function guard(dependencies: unknown[], templateFn: () => unknown): unknown;

Usage Examples:

import { guard } from "lit/directives/guard.js";

class ExpensiveComponent extends LitElement {
  @property() data = [];
  @property() filter = "";
  
  private _renderExpensiveList(items: any[]) {
    // Expensive rendering logic
    return html`
      ${items.map(item => html`
        <div class="complex-item">${item.name}</div>
      `)}
    `;
  }
  
  render() {
    return html`
      <input 
        .value=${this.filter}
        @input=${(e) => this.filter = e.target.value}
      />
      
      ${guard([this.data, this.filter], () => {
        const filteredData = this.data.filter(item => 
          item.name.includes(this.filter)
        );
        return this._renderExpensiveList(filteredData);
      })}
    `;
  }
}

Keyed Directive

/**
 * Associates a key with a template for efficient updates
 * Forces DOM re-creation when key changes
 */
function keyed(key: unknown, template: unknown): DirectiveResult;

Async Content

Directives for handling asynchronous content and streaming data.

Until Directive

/**
 * Shows placeholder content until promise resolves
 * Supports multiple promises for progressive loading
 */
function until(...values: unknown[]): DirectiveResult;

Usage Examples:

import { until } from "lit/directives/until.js";

class AsyncContent extends LitElement {
  @property() userId?: string;
  
  private async _fetchUser(id: string) {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
  }
  
  render() {
    return html`
      ${this.userId ? until(
        this._fetchUser(this.userId).then(user => html`
          <div class="user-profile">
            <h2>${user.name}</h2>
            <p>${user.email}</p>
          </div>
        `),
        html`<div class="loading">Loading user...</div>`
      ) : html`<p>No user selected</p>`}
    `;
  }
}

Async Append/Replace Directives

/**
 * Appends values from async iterable as they arrive
 * Useful for streaming content
 */
function asyncAppend(asyncIterable: AsyncIterable<unknown>): DirectiveResult;

/**
 * Replaces content with values from async iterable
 * Shows only the latest value from the stream
 */
function asyncReplace(asyncIterable: AsyncIterable<unknown>): DirectiveResult;

Usage Examples:

import { asyncAppend } from "lit/directives/async-append.js";
import { asyncReplace } from "lit/directives/async-replace.js";

class StreamingContent extends LitElement {
  private async* _generateMessages() {
    for (let i = 0; i < 5; i++) {
      await new Promise(resolve => setTimeout(resolve, 1000));
      yield html`<p>Message ${i + 1}</p>`;
    }
  }
  
  private async* _generateStatus() {
    const statuses = ["Connecting...", "Loading...", "Processing...", "Complete!"];
    for (const status of statuses) {
      await new Promise(resolve => setTimeout(resolve, 800));
      yield html`<div class="status">${status}</div>`;
    }
  }
  
  render() {
    return html`
      <div class="messages">
        ${asyncAppend(this._generateMessages())}
      </div>
      
      <div class="status-area">
        ${asyncReplace(this._generateStatus())}
      </div>
    `;
  }
}

Utility Directives

Range Directive

/**
 * Generates numeric ranges
 * Multiple overloads for different range specifications
 */
function range(end: number): Iterable<number>;
function range(start: number, end: number): Iterable<number>;
function range(start: number, end: number, step: number): Iterable<number>;

Usage Examples:

import { range } from "lit/directives/range.js";
import { map } from "lit/directives/map.js";

class NumberList extends LitElement {
  render() {
    return html`
      <ul>
        ${map(range(1, 11), (n) => html`<li>Item ${n}</li>`)}
      </ul>
      
      <div class="even-numbers">
        ${map(range(0, 21, 2), (n) => html`<span>${n}</span>`)}
      </div>
    `;
  }
}

Live Directive

/**
 * Sets property/attribute only when different from live DOM value
 * Prevents interference with user input
 */
function live(value: unknown): DirectiveResult;

Usage Examples:

import { live } from "lit/directives/live.js";

class FormInput extends LitElement {
  @property() value = "";
  
  render() {
    return html`
      <input 
        .value=${live(this.value)}
        @input=${(e) => this.value = e.target.value}
      />
    `;
  }
}

Ref Directive

/**
 * Gets a reference to the rendered element
 * Supports both Ref objects and callback functions
 */
function ref(refOrCallback: Ref | ((el: Element | undefined) => void)): DirectiveResult;

/**
 * Creates a Ref object for element references
 */
function createRef<T extends Element = Element>(): Ref<T>;

/** Ref object type */
interface Ref<T extends Element = Element> {
  readonly value?: T;
}

/** Ref or callback union type */
type RefOrCallback = Ref | ((el: Element | undefined) => void);

Usage Examples:

import { ref, createRef } from "lit/directives/ref.js";

class RefExample extends LitElement {
  private _inputRef = createRef<HTMLInputElement>();
  
  private _focusInput() {
    this._inputRef.value?.focus();
  }
  
  private _handleButtonRef = (el?: Element) => {
    if (el) {
      console.log("Button element:", el);
    }
  };
  
  render() {
    return html`
      <input ${ref(this._inputRef)} type="text" />
      <button 
        ${ref(this._handleButtonRef)}
        @click=${this._focusInput}
      >
        Focus Input
      </button>
    `;
  }
}

Unsafe Content

Directives for rendering raw HTML, SVG, and MathML content. Use with caution as these bypass Lit's automatic sanitization.

Unsafe HTML Directive

/**
 * Renders raw HTML content without sanitization
 * WARNING: Only use with trusted content to prevent XSS attacks
 */
function unsafeHTML(value: string): DirectiveResult;

Usage Examples:

import { unsafeHTML } from "lit/directives/unsafe-html.js";

class RichContent extends LitElement {
  @property() htmlContent = "";
  
  render() {
    // Only use with trusted content!
    const trustedHTML = "<strong>Bold</strong> and <em>italic</em> text";
    
    return html`
      <div class="rich-content">
        ${unsafeHTML(trustedHTML)}
      </div>
      
      <!-- WARNING: Never do this with user input -->
      <!-- ${unsafeHTML(this.htmlContent)} -->
    `;
  }
}

Unsafe SVG Directive

/**
 * Renders raw SVG content without sanitization
 * WARNING: Only use with trusted content to prevent XSS attacks
 */
function unsafeSVG(value: string): DirectiveResult;

Usage Examples:

import { unsafeSVG } from "lit/directives/unsafe-svg.js";

class IconRenderer extends LitElement {
  @property() iconData = "";
  
  render() {
    const trustedSVG = `<circle cx="50" cy="50" r="40" fill="blue" />`;
    
    return html`
      <svg viewBox="0 0 100 100">
        ${unsafeSVG(trustedSVG)}
      </svg>
    `;
  }
}

Unsafe MathML Directive

/**
 * Renders raw MathML content without sanitization
 * WARNING: Only use with trusted content to prevent XSS attacks
 */
function unsafeMathML(value: string): DirectiveResult;

Usage Examples:

import { unsafeMathML } from "lit/directives/unsafe-mathml.js";

class MathExpression extends LitElement {
  @property() formula = "";
  
  render() {
    const quadraticFormula = `
      <mrow>
        <mi>x</mi>
        <mo>=</mo>
        <mfrac>
          <mrow>
            <mo>-</mo>
            <mi>b</mi>
            <mo>±</mo>
            <msqrt>
              <mrow>
                <msup><mi>b</mi><mn>2</mn></msup>
                <mo>-</mo>
                <mn>4</mn><mi>a</mi><mi>c</mi>
              </mrow>
            </msqrt>
          </mrow>
          <mrow>
            <mn>2</mn><mi>a</mi>
          </mrow>
        </mfrac>
      </mrow>
    `;
    
    return html`
      <math xmlns="http://www.w3.org/1998/Math/MathML">
        ${unsafeMathML(quadraticFormula)}
      </math>
    `;
  }
}

Template Content Directive

/**
 * Clones and renders content from a template element
 * Useful for working with existing HTML templates
 */
function templateContent(templateElement: HTMLTemplateElement): DirectiveResult;

Usage Examples:

import { templateContent } from "lit/directives/template-content.js";

class TemplateRenderer extends LitElement {
  @query('#existing-template') templateEl!: HTMLTemplateElement;
  
  render() {
    return html`
      <!-- Existing template in DOM -->
      <template id="existing-template">
        <div class="legacy-content">
          <h3>Legacy Template</h3>
          <p>Content from existing template element</p>
        </div>
      </template>
      
      <!-- Render the template content -->
      <div class="rendered-content">
        ${this.templateEl ? templateContent(this.templateEl) : ''}
      </div>
    `;
  }
}

Types

Directive Result Types

/** Base directive result interface */
interface DirectiveResult<T extends DirectiveClass = DirectiveClass> {
  _$litDirective$: T;
  values: DirectiveParameters<InstanceType<T>>;
}

/** Directive class interface */
interface DirectiveClass {
  new (partInfo: PartInfo): Directive;
}

/** Part information passed to directives */
interface PartInfo {
  readonly type: PartType;
  readonly name?: string;
  readonly strings?: ReadonlyArray<string>;
}

/** Template part types */
enum PartType {
  ATTRIBUTE = 1,
  CHILD = 2,
  PROPERTY = 3,
  BOOLEAN_ATTRIBUTE = 4,
  EVENT = 5,
  ELEMENT = 6
}

Collection Types

/** Key function for repeat directive */
type KeyFn<T> = (item: T, index: number) => unknown;

/** Item template function for repeat directive */
type ItemTemplate<T> = (item: T, index: number) => unknown;

/** Class information for classMap directive */
interface ClassInfo {
  [name: string]: string | boolean | number;
}

/** Style information for styleMap directive */
interface StyleInfo {
  [name: string]: string | number | undefined | null;
}