@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
| Parameter | Type | Required | Description |
|---|---|---|---|
selector | string | One of selector/ref | CSS selector to match elements. |
ref | string | One of selector/ref | Value of data-ref attribute to match. |
all | boolean | No | Query for all matching elements (default: false). |
cache | boolean | No | Cache the query result (default: false). |
scope | 'light' | 'shadow' | 'both' | No | Which 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.
| Scope | Searches |
|---|---|
'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
getRefand other utility methods.