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

dom-query-decorators.mddocs/

DOM Query Decorators

Decorators for querying elements in the component's shadow DOM and slot content with automatic updates when DOM changes.

Capabilities

Query Decorator

Property decorator for querying a single element in the component's shadow DOM.

/**
 * Property decorator that queries for a single element in shadow DOM
 * Returns the first matching element or null if not found
 */
function query(selector: string): PropertyDecorator;

type QueryDecorator = typeof query;

Usage Examples:

import { LitElement, html } from "lit";
import { customElement, query } from "lit/decorators.js";

@customElement("form-component")
class FormComponent extends LitElement {
  @query("input") input!: HTMLInputElement;
  @query("#submit-btn") submitButton!: HTMLButtonElement;
  @query(".error-message") errorMessage!: HTMLElement | null;
  
  private _handleSubmit() {
    const value = this.input.value;
    if (!value) {
      if (this.errorMessage) {
        this.errorMessage.textContent = "Input required";
      }
    }
  }
  
  render() {
    return html`
      <form @submit=${this._handleSubmit}>
        <input type="text" />
        <button id="submit-btn">Submit</button>
        <div class="error-message"></div>
      </form>
    `;
  }
}

Query All Decorator

Property decorator for querying multiple elements in the component's shadow DOM.

/**
 * Property decorator that queries for all matching elements in shadow DOM
 * Returns a NodeList of all matching elements
 */
function queryAll(selector: string): PropertyDecorator;

type QueryAllDecorator = typeof queryAll;

Usage Examples:

import { LitElement, html } from "lit";
import { customElement, queryAll } from "lit/decorators.js";

@customElement("tab-container")
class TabContainer extends LitElement {
  @queryAll(".tab") tabs!: NodeListOf<HTMLElement>;
  @queryAll("button[role='tab']") tabButtons!: NodeListOf<HTMLButtonElement>;
  
  private _activateTab(index: number) {
    // Deactivate all tabs
    this.tabs.forEach(tab => tab.classList.remove("active"));
    this.tabButtons.forEach(btn => btn.setAttribute("aria-selected", "false"));
    
    // Activate selected tab
    this.tabs[index]?.classList.add("active");
    this.tabButtons[index]?.setAttribute("aria-selected", "true");
  }
  
  render() {
    return html`
      <div class="tab-list">
        <button role="tab" @click=${() => this._activateTab(0)}>Tab 1</button>
        <button role="tab" @click=${() => this._activateTab(1)}>Tab 2</button>
      </div>
      <div class="tab active">Panel 1</div>
      <div class="tab">Panel 2</div>
    `;
  }
}

Query Async Decorator

Property decorator for async element queries using the component's updateComplete promise.

/**
 * Property decorator for async element queries
 * Returns a promise that resolves to the element after update completes
 */
function queryAsync(selector: string): PropertyDecorator;

type QueryAsyncDecorator = typeof queryAsync;

Usage Examples:

import { LitElement, html } from "lit";
import { customElement, queryAsync, state } from "lit/decorators.js";

@customElement("dynamic-content")
class DynamicContent extends LitElement {
  @queryAsync("#dynamic-element") dynamicElement!: Promise<HTMLElement>;
  @state() private _showElement = false;
  
  private async _focusElement() {
    this._showElement = true;
    const element = await this.dynamicElement;
    element?.focus();
  }
  
  render() {
    return html`
      <button @click=${this._focusElement}>Show and Focus</button>
      ${this._showElement ? html`
        <input id="dynamic-element" type="text" />
      ` : ""}
    `;
  }
}

Query Assigned Elements Decorator

Property decorator for querying elements assigned to slots in the component's shadow DOM.

/**
 * Property decorator for querying elements assigned to slots
 * Returns elements distributed to the component's slots
 */
function queryAssignedElements(
  options?: QueryAssignedElementsOptions
): PropertyDecorator;

type QueryAssignedElementsDecorator = typeof queryAssignedElements;

/** Configuration options for querying assigned elements */
interface QueryAssignedElementsOptions {
  /** Slot name to query (default: unnamed slot) */
  slot?: string;
  
  /** CSS selector to filter assigned elements */
  selector?: string;
  
  /** Whether to flatten nested slots */
  flatten?: boolean;
}

Usage Examples:

import { LitElement, html } from "lit";
import { customElement, queryAssignedElements } from "lit/decorators.js";

@customElement("card-container")
class CardContainer extends LitElement {
  // Query all elements assigned to default slot
  @queryAssignedElements() defaultSlotElements!: HTMLElement[];
  
  // Query elements assigned to named slot
  @queryAssignedElements({ slot: "header" }) headerElements!: HTMLElement[];
  
  // Query specific elements in slot
  @queryAssignedElements({ 
    slot: "content", 
    selector: ".card" 
  }) cardElements!: HTMLElement[];
  
  private _handleSlotChange() {
    console.log("Default slot elements:", this.defaultSlotElements.length);
    console.log("Header elements:", this.headerElements.length);
    console.log("Card elements:", this.cardElements.length);
  }
  
  render() {
    return html`
      <div class="container">
        <header>
          <slot name="header" @slotchange=${this._handleSlotChange}></slot>
        </header>
        <main>
          <slot name="content"></slot>
        </main>
        <footer>
          <slot @slotchange=${this._handleSlotChange}></slot>
        </footer>
      </div>
    `;
  }
}

// Usage:
// <card-container>
//   <h1 slot="header">Title</h1>
//   <div slot="content" class="card">Card 1</div>
//   <div slot="content" class="card">Card 2</div>
//   <p>Footer content</p>
// </card-container>

Query Assigned Nodes Decorator

Property decorator for querying all nodes (including text nodes) assigned to slots.

/**
 * Property decorator for querying all nodes assigned to slots
 * Returns all nodes (elements and text) distributed to slots
 */
function queryAssignedNodes(
  options?: QueryAssignedNodesOptions
): PropertyDecorator;

type QueryAssignedNodesDecorator = typeof queryAssignedNodes;

/** Configuration options for querying assigned nodes */
interface QueryAssignedNodesOptions {
  /** Slot name to query (default: unnamed slot) */
  slot?: string;
  
  /** Whether to flatten nested slots */
  flatten?: boolean;
}

Usage Examples:

import { LitElement, html } from "lit";
import { customElement, queryAssignedNodes } from "lit/decorators.js";

@customElement("text-processor")
class TextProcessor extends LitElement {
  @queryAssignedNodes() allNodes!: Node[];
  @queryAssignedNodes({ slot: "content" }) contentNodes!: Node[];
  
  private _processText() {
    const textContent = this.allNodes
      .filter(node => node.nodeType === Node.TEXT_NODE)
      .map(node => node.textContent)
      .join("");
    
    console.log("All text content:", textContent);
  }
  
  render() {
    return html`
      <div>
        <slot name="content" @slotchange=${this._processText}></slot>
        <slot @slotchange=${this._processText}></slot>
      </div>
    `;
  }
}

// Usage:
// <text-processor>
//   <span slot="content">Hello</span>
//   World
//   <em>!</em>
// </text-processor>

Types

Query Result Types

/** Single element query result */
type QueryResult<T extends Element = Element> = T | null;

/** Multiple elements query result */
type QueryAllResult<T extends Element = Element> = NodeListOf<T>;

/** Async element query result */
type QueryAsyncResult<T extends Element = Element> = Promise<T | null>;

/** Assigned elements query result */
type AssignedElementsResult = HTMLElement[];

/** Assigned nodes query result */
type AssignedNodesResult = Node[];

Query Options Types

/** Options for querying assigned elements */
interface QueryAssignedElementsOptions {
  /** Name of the slot to query (optional) */
  slot?: string;
  
  /** CSS selector to filter results (optional) */
  selector?: string;
  
  /** Whether to flatten nested slot content */
  flatten?: boolean;
}

/** Options for querying assigned nodes */
interface QueryAssignedNodesOptions {
  /** Name of the slot to query (optional) */
  slot?: string;
  
  /** Whether to flatten nested slot content */
  flatten?: boolean;
}

Usage Patterns

Safe Element Access

Always check for null when using @query() since elements might not exist:

@query("#optional-element") optionalElement?: HTMLElement;

private _useElement() {
  if (this.optionalElement) {
    this.optionalElement.focus();
  }
}

Type Safety with Generic Queries

Use TypeScript generics for type-safe element queries:

@query("canvas") canvas!: HTMLCanvasElement;
@query("video") video!: HTMLVideoElement;
@queryAll("input") inputs!: NodeListOf<HTMLInputElement>;

Slot Change Handling

React to slot content changes by listening to slotchange events:

@queryAssignedElements() slotElements!: HTMLElement[];

private _handleSlotChange() {
  // React to changes in slotted content
  this.slotElements.forEach(el => {
    // Process each assigned element
  });
}

render() {
  return html`
    <slot @slotchange=${this._handleSlotChange}></slot>
  `;
}

Performance Considerations

  • Query decorators cache results until the next update cycle
  • Use @queryAsync() when elements are conditionally rendered
  • Prefer @query() over @queryAll() when you only need the first match
  • Assigned element/node queries are updated on slot changes