CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-lit-element

A simple base class for creating fast, lightweight web components

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

directives.mddocs/

Directives

Built-in directives for advanced templating patterns, conditional rendering, and performance optimization. Directives extend lit-html's templating capabilities with reusable, efficient patterns for common UI scenarios.

Capabilities

Repeat Directive

Efficiently renders lists with keyed items for optimal performance.

/**
 * Renders a list of items with efficient updates using keys
 * @param items Iterable of items to render
 * @param keyFn Function to generate unique keys for items
 * @param template Function to render each item
 * @returns DirectiveResult for list rendering
 */
function repeat<T>(
  items: Iterable<T>,
  keyFn: KeyFn<T>,
  template: ItemTemplate<T>
): DirectiveResult<typeof RepeatDirective>;

type KeyFn<T> = (item: T, index: number) => unknown;
type ItemTemplate<T> = (item: T, index: number) => unknown;

Usage Examples:

import { LitElement, html, repeat } from "lit-element";

interface User {
  id: number;
  name: string;
  email: string;
}

class UserList extends LitElement {
  users: User[] = [
    { id: 1, name: "Alice", email: "alice@example.com" },
    { id: 2, name: "Bob", email: "bob@example.com" },
    { id: 3, name: "Charlie", email: "charlie@example.com" }
  ];

  render() {
    return html`
      <ul>
        ${repeat(
          this.users,
          (user) => user.id, // Key function
          (user, index) => html` // Template function
            <li>
              <strong>${user.name}</strong> (${index + 1})
              <br />
              <small>${user.email}</small>
            </li>
          `
        )}
      </ul>
    `;
  }
}

Class Map Directive

Conditionally applies CSS classes based on an object map.

/**
 * Sets CSS classes on an element based on a map of class names to boolean values
 * @param classInfo Object mapping class names to boolean conditions
 * @returns DirectiveResult for conditional class application
 */
function classMap(classInfo: ClassInfo): DirectiveResult<typeof ClassMapDirective>;

interface ClassInfo {
  [name: string]: string | boolean | number;
}

Usage Examples:

import { LitElement, html, classMap } from "lit-element";

class ButtonElement extends LitElement {
  disabled = false;
  primary = true;
  loading = false;
  size = "medium";

  render() {
    const classes = {
      'btn': true,
      'btn-primary': this.primary,
      'btn-secondary': !this.primary,
      'btn-disabled': this.disabled,
      'btn-loading': this.loading,
      [`btn-${this.size}`]: this.size
    };

    return html`
      <button class=${classMap(classes)} ?disabled=${this.disabled}>
        ${this.loading ? 'Loading...' : 'Click me'}
      </button>
    `;
  }
}

Style Map Directive

Conditionally applies CSS styles based on an object map.

/**
 * Sets CSS styles on an element based on a map of style properties to values
 * @param styleInfo Object mapping CSS properties to values
 * @returns DirectiveResult for conditional style application
 */
function styleMap(styleInfo: StyleInfo): DirectiveResult<typeof StyleMapDirective>;

interface StyleInfo {
  [name: string]: string | number | undefined | null;
}

Usage Examples:

import { LitElement, html, styleMap } from "lit-element";

class ProgressBar extends LitElement {
  progress = 50; // 0-100
  color = "#007bff";
  height = 20;

  render() {
    const barStyles = {
      width: `${this.progress}%`,
      height: `${this.height}px`,
      backgroundColor: this.color,
      transition: 'width 0.3s ease'
    };

    const containerStyles = {
      width: '100%',
      height: `${this.height}px`,
      backgroundColor: '#e9ecef',
      borderRadius: '4px',
      overflow: 'hidden'
    };

    return html`
      <div style=${styleMap(containerStyles)}>
        <div style=${styleMap(barStyles)}></div>
      </div>
      <p>${this.progress}% complete</p>
    `;
  }
}

When Directive

Conditionally renders content based on a boolean condition.

/**
 * Conditionally renders one of two templates based on a condition
 * @param condition Boolean condition to evaluate
 * @param trueCase Template to render when condition is true
 * @param falseCase Template to render when condition is false
 * @returns Rendered template or nothing
 */
function when<T, F>(
  condition: boolean,
  trueCase: () => T,
  falseCase?: () => F
): T | F | typeof nothing;

Usage Examples:

import { LitElement, html, when } from "lit-element";

class ConditionalElement extends LitElement {
  isLoggedIn = false;
  userName = "Alice";
  hasPermission = true;

  render() {
    return html`
      <div>
        ${when(
          this.isLoggedIn,
          () => html`
            <div class="user-area">
              <h2>Welcome, ${this.userName}!</h2>
              ${when(
                this.hasPermission,
                () => html`<button>Admin Panel</button>`,
                () => html`<p>Limited access</p>`
              )}
            </div>
          `,
          () => html`
            <div class="login-area">
              <h2>Please log in</h2>
              <button>Login</button>
            </div>
          `
        )}
      </div>
    `;
  }
}

Choose Directive

Renders different templates based on a switch-like pattern.

/**
 * Renders different templates based on a value, like a switch statement
 * @param value Value to switch on
 * @param cases Object mapping values to template functions
 * @param defaultCase Default template when no case matches
 * @returns Rendered template based on value
 */
function choose<T, V>(
  value: T,
  cases: Record<string, () => V>,
  defaultCase?: () => V
): V | typeof nothing;

Usage Examples:

import { LitElement, html, choose } from "lit-element";

type Status = 'loading' | 'success' | 'error' | 'idle';

class StatusElement extends LitElement {
  status: Status = 'idle';
  message = '';

  render() {
    return html`
      <div class="status-display">
        ${choose(this.status, {
          loading: () => html`
            <div class="spinner"></div>
            <p>Loading...</p>
          `,
          success: () => html`
            <div class="success-icon">✓</div>
            <p>Success! ${this.message}</p>
          `,
          error: () => html`
            <div class="error-icon">✗</div>
            <p>Error: ${this.message}</p>
          `,
          idle: () => html`
            <p>Ready to start</p>
            <button @click=${this._start}>Start</button>
          `
        })}
      </div>
    `;
  }

  private _start() {
    this.status = 'loading';
    // Simulate async operation
    setTimeout(() => {
      this.status = Math.random() > 0.5 ? 'success' : 'error';
      this.message = this.status === 'success' ? 'Operation completed' : 'Something went wrong';
    }, 2000);
  }
}

If Defined Directive

Only sets an attribute if the value is defined (not null or undefined).

/**
 * Sets an attribute only if the value is defined (not null or undefined)
 * @param value Value to conditionally set
 * @returns Value or nothing if undefined/null
 */
function ifDefined(value: unknown): unknown | typeof nothing;

Usage Examples:

import { LitElement, html, ifDefined } from "lit-element";

class ImageElement extends LitElement {
  src?: string;
  alt?: string;
  title?: string;
  width?: number;
  height?: number;

  render() {
    return html`
      <img
        src=${ifDefined(this.src)}
        alt=${ifDefined(this.alt)}
        title=${ifDefined(this.title)}
        width=${ifDefined(this.width)}
        height=${ifDefined(this.height)}
      />
    `;
  }
}

// Only defined attributes will be set:
// <img src="image.jpg" alt="Description" /> (title, width, height omitted)

Guard Directive

Prevents re-evaluation of expensive templates unless dependencies change.

/**
 * Guards template re-evaluation by checking if dependencies have changed
 * @param value Value or values to watch for changes
 * @param fn Function that returns the template to render
 * @returns Guarded template result
 */
function guard(value: unknown, fn: () => unknown): DirectiveResult<typeof GuardDirective>;

Usage Examples:

import { LitElement, html, guard } from "lit-element";

class ExpensiveList extends LitElement {
  data: any[] = [];
  sortOrder = 'asc';
  filterText = '';

  private _processData() {
    console.log('Processing data (expensive operation)');
    return this.data
      .filter(item => item.name.toLowerCase().includes(this.filterText.toLowerCase()))
      .sort((a, b) => {
        const order = this.sortOrder === 'asc' ? 1 : -1;
        return a.name.localeCompare(b.name) * order;
      });
  }

  render() {
    return html`
      <div>
        <input
          @input=${(e: any) => this.filterText = e.target.value}
          placeholder="Filter..."
        />
        <button @click=${() => this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc'}>
          Sort ${this.sortOrder === 'asc' ? '↓' : '↑'}
        </button>
        
        <!-- Only re-process when data, sortOrder, or filterText change -->
        ${guard([this.data, this.sortOrder, this.filterText], () => {
          const processedData = this._processData();
          return html`
            <ul>
              ${processedData.map(item => html`<li>${item.name}</li>`)}
            </ul>
          `;
        })}
      </div>
    `;
  }
}

Cache Directive

Caches template results based on a key for performance optimization.

/**
 * Caches template results based on a value/key
 * @param value Key value for caching
 * @returns Cached directive result
 */
function cache(value: unknown): DirectiveResult<typeof CacheDirective>;

Usage Examples:

import { LitElement, html, cache } from "lit-element";

class TabContainer extends LitElement {
  activeTab = 'tab1';
  tabs = ['tab1', 'tab2', 'tab3'];

  private _renderTabContent(tabId: string) {
    // Expensive template rendering
    console.log(`Rendering content for ${tabId}`);
    return html`
      <div class="tab-content">
        <h3>Content for ${tabId}</h3>
        <p>This is expensive content that benefits from caching.</p>
        <ul>
          ${Array.from({length: 100}, (_, i) => html`<li>Item ${i + 1}</li>`)}
        </ul>
      </div>
    `;
  }

  render() {
    return html`
      <div class="tabs">
        ${this.tabs.map(tab => html`
          <button
            class=${tab === this.activeTab ? 'active' : ''}
            @click=${() => this.activeTab = tab}
          >
            ${tab}
          </button>
        `)}
      </div>
      
      <!-- Cache expensive content for each tab -->
      ${cache(this.activeTab)}
      ${this.activeTab === 'tab1' ? this._renderTabContent('tab1') : ''}
      ${this.activeTab === 'tab2' ? this._renderTabContent('tab2') : ''}
      ${this.activeTab === 'tab3' ? this._renderTabContent('tab3') : ''}
    `;
  }
}

Live Directive

Forces a property binding to always update, even if the value hasn't changed.

/**
 * Forces live binding that always updates regardless of value changes
 * @param value Value to bind with live updates
 * @returns Live binding directive result
 */
function live(value: unknown): DirectiveResult<typeof LiveDirective>;

Usage Examples:

import { LitElement, html, live } from "lit-element";

class InputElement extends LitElement {
  value = '';

  render() {
    return html`
      <div>
        <!-- Regular binding might not update if user modifies input -->
        <input .value=${this.value} @input=${this._updateValue} />
        
        <!-- Live binding ensures input always reflects property value -->
        <input .value=${live(this.value)} @input=${this._updateValue} />
        
        <button @click=${this._reset}>Reset</button>
        <p>Current value: ${this.value}</p>
      </div>
    `;
  }

  private _updateValue(e: Event) {
    this.value = (e.target as HTMLInputElement).value;
  }

  private _reset() {
    this.value = '';
    // Live binding ensures input is cleared even if user has modified it
  }
}

Ref Directive

Creates references to DOM elements for imperative access.

/**
 * Creates a reference to a DOM element
 * @param callback Optional callback function called with the element
 * @returns Ref directive result
 */
function ref(callback?: (element?: Element) => void): DirectiveResult<typeof RefDirective>;

Usage Examples:

import { LitElement, html, ref, createRef } from "lit-element";

class RefElement extends LitElement {
  private inputRef = createRef<HTMLInputElement>();
  private canvasRef = createRef<HTMLCanvasElement>();

  render() {
    return html`
      <div>
        <input ${ref(this.inputRef)} placeholder="Focus me" />
        <canvas ${ref(this.canvasRef)} width="200" height="100"></canvas>
        <button @click=${this._focusInput}>Focus Input</button>
        <button @click=${this._drawOnCanvas}>Draw</button>
      </div>
    `;
  }

  private _focusInput() {
    this.inputRef.value?.focus();
  }

  private _drawOnCanvas() {
    const canvas = this.canvasRef.value;
    if (canvas) {
      const ctx = canvas.getContext('2d');
      if (ctx) {
        ctx.fillStyle = 'blue';
        ctx.fillRect(10, 10, 100, 50);
        ctx.fillStyle = 'white';
        ctx.font = '16px Arial';
        ctx.fillText('Hello!', 20, 35);
      }
    }
  }
}

Until Directive

Renders placeholder content until a promise resolves.

/**
 * Renders default content until a promise resolves, then renders the resolved value
 * @param promise Promise to wait for
 * @param defaultContent Content to show while waiting
 * @returns Until directive result
 */
function until(promise: Promise<unknown>, ...defaultContent: unknown[]): DirectiveResult<typeof UntilDirective>;

Usage Examples:

import { LitElement, html, until } from "lit-element";

class AsyncElement extends LitElement {
  private async _loadUserData(userId: number) {
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 2000));
    return { id: userId, name: `User ${userId}`, email: `user${userId}@example.com` };
  }

  private async _loadPosts() {
    await new Promise(resolve => setTimeout(resolve, 1500));
    return ['Post 1', 'Post 2', 'Post 3'];
  }

  render() {
    const userData = this._loadUserData(123);
    const posts = this._loadPosts();

    return html`
      <div>
        <h2>User Profile</h2>
        
        <!-- Show loading message until user data loads -->
        ${until(
          userData.then(user => html`
            <div class="user-info">
              <h3>${user.name}</h3>
              <p>Email: ${user.email}</p>
              <p>ID: ${user.id}</p>
            </div>
          `),
          html`<p>Loading user data...</p>`
        )}

        <h3>Recent Posts</h3>
        
        <!-- Show spinner until posts load -->
        ${until(
          posts.then(postList => html`
            <ul>
              ${postList.map(post => html`<li>${post}</li>`)}
            </ul>
          `),
          html`<div class="spinner">Loading posts...</div>`
        )}
      </div>
    `;
  }
}

Unsafe HTML/SVG Directives

Renders untrusted HTML/SVG content (use with extreme caution).

/**
 * Renders HTML string as actual HTML (bypasses sanitization)
 * WARNING: Only use with trusted content to prevent XSS attacks
 * @param html HTML string to render
 * @returns Unsafe HTML directive result
 */
function unsafeHTML(html: string): DirectiveResult<typeof UnsafeHTMLDirective>;

/**
 * Renders SVG string as actual SVG (bypasses sanitization) 
 * WARNING: Only use with trusted content to prevent XSS attacks
 * @param svg SVG string to render
 * @returns Unsafe SVG directive result
 */
function unsafeSVG(svg: string): DirectiveResult<typeof UnsafeSVGDirective>;

Usage Examples:

import { LitElement, html, unsafeHTML, unsafeSVG } from "lit-element";

class UnsafeContentElement extends LitElement {
  // ONLY use with trusted content!
  trustedHtml = '<strong>Bold</strong> and <em>italic</em> text';
  trustedSvg = '<circle cx="50" cy="50" r="25" fill="red" />';

  render() {
    return html`
      <div>
        <h3>Trusted HTML Content:</h3>
        <div>${unsafeHTML(this.trustedHtml)}</div>
        
        <h3>Trusted SVG Content:</h3>
        <svg width="100" height="100">
          ${unsafeSVG(this.trustedSvg)}
        </svg>
      </div>
    `;
  }
}

Unsafe MathML Directive

Renders MathML string as actual MathML (bypasses sanitization).

/**
 * Renders MathML string as actual MathML (bypasses sanitization)
 * WARNING: Only use with trusted content to prevent XSS attacks
 * @param mathml MathML string to render
 * @returns Unsafe MathML directive result
 */
function unsafeMathML(mathml: string): DirectiveResult<typeof UnsafeMathMLDirective>;

Async Append Directive

Appends values from an async iterable to the DOM.

/**
 * Appends values from an async iterable, preserving previous values
 * @param asyncIterable Async iterable of values to append
 * @returns Async append directive result
 */
function asyncAppend(asyncIterable: AsyncIterable<unknown>): DirectiveResult<typeof AsyncAppendDirective>;

Usage Examples:

import { LitElement, html, asyncAppend } from "lit-element";

class StreamingElement extends LitElement {
  async *generateData() {
    for (let i = 0; i < 10; i++) {
      await new Promise(resolve => setTimeout(resolve, 1000));
      yield html`<div>Item ${i + 1} - ${new Date().toLocaleTimeString()}</div>`;
    }
  }

  render() {
    return html`
      <div>
        <h3>Streaming Data (Append):</h3>
        <div class="stream">
          ${asyncAppend(this.generateData())}
        </div>
      </div>
    `;
  }
}

Async Replace Directive

Replaces content with values from an async iterable.

/**
 * Replaces content with values from an async iterable
 * @param asyncIterable Async iterable of values to render
 * @returns Async replace directive result
 */
function asyncReplace(asyncIterable: AsyncIterable<unknown>): DirectiveResult<typeof AsyncReplaceDirective>;

Usage Examples:

import { LitElement, html, asyncReplace } from "lit-element";

class CountdownElement extends LitElement {
  async *countdown(from: number) {
    for (let i = from; i >= 0; i--) {
      yield html`<div class="countdown">${i}</div>`;
      if (i > 0) {
        await new Promise(resolve => setTimeout(resolve, 1000));
      }
    }
    yield html`<div class="done">🎉 Done!</div>`;
  }

  render() {
    return html`
      <div>
        <h3>Countdown Timer:</h3>
        ${asyncReplace(this.countdown(5))}
      </div>
    `;
  }
}

Map Directive

Maps over an iterable with a template function.

/**
 * Maps over an iterable, applying a template function to each item
 * @param items Iterable to map over
 * @param template Function to apply to each item
 * @returns Map directive result
 */
function map<T>(
  items: Iterable<T>,
  template: (item: T, index: number) => unknown
): DirectiveResult<typeof MapDirective>;

Usage Examples:

import { LitElement, html, map } from "lit-element";

class MappedList extends LitElement {
  items = ['apple', 'banana', 'cherry'];
  users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' }
  ];

  render() {
    return html`
      <div>
        <h3>Simple List:</h3>
        <ul>
          ${map(this.items, (item, index) => html`
            <li>${index + 1}. ${item}</li>
          `)}
        </ul>

        <h3>User Cards:</h3>
        <div class="users">
          ${map(this.users, (user) => html`
            <div class="user-card">
              <h4>${user.name}</h4>
              <p>ID: ${user.id}</p>
            </div>
          `)}
        </div>
      </div>
    `;
  }
}

Join Directive

Joins rendered items with a separator.

/**
 * Joins rendered items with a separator
 * @param items Iterable of items to join
 * @param joiner Separator to place between items
 * @returns Join directive result
 */
function join<T>(
  items: Iterable<T>,
  joiner: unknown
): DirectiveResult<typeof JoinDirective>;

Usage Examples:

import { LitElement, html, join } from "lit-element";

class JoinedList extends LitElement {
  tags = ['javascript', 'typescript', 'lit-element'];
  breadcrumbs = ['Home', 'Products', 'Electronics', 'Phones'];

  render() {
    return html`
      <div>
        <h3>Tags:</h3>
        <p>
          ${join(
            this.tags.map(tag => html`<span class="tag">${tag}</span>`),
            html`<span class="separator"> • </span>`
          )}
        </p>

        <h3>Breadcrumbs:</h3>
        <nav>
          ${join(
            this.breadcrumbs.map(crumb => html`<a href="#">${crumb}</a>`),
            html`<span> / </span>`
          )}
        </nav>

        <h3>Simple Join:</h3>
        <p>${join(this.breadcrumbs, ' > ')}</p>
      </div>
    `;
  }
}

Range Directive

Generates a range of numbers for iteration.

/**
 * Generates a range of numbers
 * @param startOrEnd Start value (if end provided) or end value (if only parameter)
 * @param end End value (exclusive)
 * @param step Step size (default: 1)
 * @returns Range directive result
 */
function range(startOrEnd: number, end?: number, step?: number): Iterable<number>;

Usage Examples:

import { LitElement, html, range, map } from "lit-element";

class RangeElement extends LitElement {
  render() {
    return html`
      <div>
        <h3>Simple Range (0 to 5):</h3>
        <ul>
          ${map(range(5), (n) => html`<li>Item ${n}</li>`)}
        </ul>

        <h3>Custom Range (10 to 20, step 2):</h3>
        <ul>
          ${map(range(10, 20, 2), (n) => html`<li>Number ${n}</li>`)}
        </ul>

        <h3>Grid (3x3):</h3>
        <div class="grid">
          ${map(range(3), (row) => html`
            <div class="row">
              ${map(range(3), (col) => html`
                <div class="cell">${row},${col}</div>
              `)}
            </div>
          `)}
        </div>
      </div>
    `;
  }
}

Keyed Directive

Forces re-rendering when a key changes.

/**
 * Associates a key with content, forcing re-render when key changes
 * @param key Key value to associate with content
 * @param value Content to render
 * @returns Keyed directive result
 */
function keyed(key: unknown, value: unknown): DirectiveResult<typeof KeyedDirective>;

Usage Examples:

import { LitElement, html, keyed } from "lit-element";

class KeyedElement extends LitElement {
  currentUser = { id: 1, name: 'Alice' };
  refreshKey = 0;

  private _switchUser() {
    this.currentUser = this.currentUser.id === 1 
      ? { id: 2, name: 'Bob' }
      : { id: 1, name: 'Alice' };
  }

  private _refresh() {
    this.refreshKey++;
  }

  render() {
    return html`
      <div>
        <button @click=${this._switchUser}>Switch User</button>
        <button @click=${this._refresh}>Force Refresh</button>
        
        <!-- Force re-render when user changes -->
        ${keyed(this.currentUser.id, html`
          <div class="user-profile">
            <h3>${this.currentUser.name}</h3>
            <input placeholder="User-specific input" />
            <p>Profile loaded at: ${new Date().toLocaleTimeString()}</p>
          </div>
        `)}

        <!-- Force re-render when refresh key changes -->
        ${keyed(this.refreshKey, html`
          <div class="refresh-content">
            <p>Refreshed content: ${Math.random()}</p>
          </div>
        `)}
      </div>
    `;
  }
}

Template Content Directive

Renders content from an HTMLTemplateElement.

/**
 * Renders the content of an HTMLTemplateElement
 * @param templateElement HTMLTemplateElement to render
 * @returns Template content directive result
 */
function templateContent(templateElement: HTMLTemplateElement): DirectiveResult<typeof TemplateContentDirective>;

Usage Examples:

import { LitElement, html, templateContent } from "lit-element";

class TemplateContentElement extends LitElement {
  private _getTemplate() {
    const template = document.createElement('template');
    template.innerHTML = `
      <div style="border: 1px solid #ccc; padding: 16px;">
        <h4>Template Content</h4>
        <p>This content came from an HTMLTemplateElement</p>
        <button>Template Button</button>
      </div>
    `;
    return template;
  }

  render() {
    return html`
      <div>
        <h3>Rendered Template:</h3>
        ${templateContent(this._getTemplate())}
      </div>
    `;
  }
}

docs

core-element.md

css-styling.md

decorators.md

directives.md

index.md

polyfill-support.md

property-system.md

template-system.md

tile.json