Radiant0.3.0-rc.10

@query

The @query decorator provides a convenient way to query (and optionally cache) references to elements within your host DOM. It creates a getter that performs the query on every access by default, or once and caches the result if you pass cache: true.

It works on both RadiantElement and RadiantController.

Usage

import { RadiantElement, customElement, query } from '@ecopages/radiant';
 
@customElement('user-profile')
export class UserProfile extends RadiantElement {
	// Query by data-ref (recommended)
	@query({ ref: 'avatar' }) avatar!: HTMLImageElement;
	
	// Query by CSS selector
	@query({ selector: 'button.action' }) submitButton!: HTMLButtonElement;
	
	// Query all matching elements
	@query({ ref: 'item', all: true }) items!: HTMLElement[];
}

Parameters

ParameterTypeRequiredDescription
selectorstringOne of selector/refCSS selector to match elements.
refstringOne of selector/refValue of data-ref attribute to match.
allbooleanNoQuery for all matching elements (default: false).
cachebooleanNoCache the query result (default: false).
scope'light' | 'shadow' | 'both'NoWhich DOM tree to search (default: 'light').

Feature Highlight

Caching Behavior

By default, queries are not cached — each access re-runs the lookup. For a stable reference that never needs to be re-resolved, pass cache: true so repeated access skips the query.

@query({ ref: 'stable-el', cache: true })
stableEl!: HTMLElement;

Shadow DOM Scope

By default, queries run against the host's light DOM. Use the scope option to query inside a shadow root or both trees.

ScopeSearches
'light' (default)Host element only
'shadow'host.shadowRoot only
'both'Light DOM first, then shadow root
@query({ selector: '.inner', scope: 'shadow' }) shadowEl!: HTMLElement;
@query({ selector: '.anywhere', scope: 'both', all: true }) allEls!: HTMLElement[];

Type Safety

Use TypeScript's non-null assertion operator (!) for required elements, or the optional chaining operator (?) if the element might not be present.

@query({ ref: 'required-el' }) element!: HTMLElement;
@query({ ref: 'optional-el' }) optional?: HTMLElement;

@query vs JSX Bindings

@query is for reading DOM you do not directly control from render() — content projected into a slot by a consumer, DOM enhanced by a RadiantController, or a live Element handle needed by a third-party imperative API (positioning libraries, focus management, observers).

If the host does not override render(), copy the field with @bindTo instead of querying a node only to write into it.

If the element is one your own render() JSX produces, prefer a binding instead of querying it back out to write into it:

// Avoid — re-fetching a host-rendered node just to write into it
@query({ ref: 'count' }) countEl!: HTMLElement;
 
@onUpdated('count')
syncCount() {
  this.countEl.textContent = String(this.count);
}
 
override render() {
  return <span data-ref="count"></span>;
}
// Prefer — bind the value directly at the JSX position
override render() {
  return <span>{this.$.count}</span>;
}

The binding patches just that text node when count changes; the query-and-write version does the same job with more code, an extra DOM read, and no benefit. Reach for @query when there's no reactive value to bind — you need the node itself, not a value at that position.

Learn More

  • Use @query({ all: true }) when you want every matching element instead of only the first one.
  • @querySlot - Query projected elements assigned to a render-owning RadiantElement slot.
  • RadiantElement - Learn about getRef and other utility methods.