--- title: "@query" description: "Query and cache references to elements within your host DOM." group: Decorators order: 7 --- # @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 ```typescript 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. ```typescript @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 | ```typescript @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. ```typescript @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](/docs/decorators/bind-to) 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: ```tsx // 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 ; } ``` ```tsx // Prefer — bind the value directly at the JSX position override render() { return {this.$.count}; } ``` 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](/docs/decorators/query-slot) - Query projected elements assigned to a render-owning RadiantElement slot. - [RadiantElement](/docs/components/radiant-element) - Learn about `getRef` and other utility methods.