Decorators for querying elements in the component's shadow DOM and slot content with automatic updates when DOM changes.
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>
`;
}
}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>
`;
}
}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" />
` : ""}
`;
}
}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>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>/** 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[];/** 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;
}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();
}
}Use TypeScript generics for type-safe element queries:
@query("canvas") canvas!: HTMLCanvasElement;
@query("video") video!: HTMLVideoElement;
@queryAll("input") inputs!: NodeListOf<HTMLInputElement>;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>
`;
}@queryAsync() when elements are conditionally rendered@query() over @queryAll() when you only need the first match