` tags
Full host HTML (`...`) is produced by the server pipeline — prefer `renderComponent(...)` from `@ecopages/radiant/server/render-component`. See [Component SSR](/docs/ssr/component-ssr).
## Render Lifecycle Gate
`RadiantElement` only runs the host render lifecycle when `render()` is overridden.
- The base `render()` returns ``.
- A plain `RadiantElement` instance with no override keeps authored children visible and does not call `update()` on first connect.
- An overridden `render()` opt-in turns the host into a JSX-owned render boundary.
This means the correct question is not "element or component?" anymore. The question is whether the host should keep authored DOM or own a JSX view.
## Connect-time sync
Override `protected onConnected()` for work that must wait until authored attributes are visible and (when the host owns `render()`) the initial hydrate/update has committed.
Do not queue a `queueMicrotask` from `connectedCallback` for that. `onConnected` is invoked at that same point, on every connection — not once per instance. Rebuild controllers, observers, and listeners here if `disconnectedCallback` tears them down. Guard once-only bootstrapping with an explicit flag.
Keep synchronous setup (event listeners, `MutationObserver`) in `connectedCallback`. Work that needs the committed DOM, such as moving focus, belongs in `updated()`.
`onConnected` is not `registerConnectedCallback()`. Those reactive-host callbacks run synchronously at the start of `connectedCallback`, before attribute catch-up.
Most elements do not need this hook. Do not use it to copy a reactive value into JSX-owned DOM; use a JSX binding. Do not use it to copy a value onto parent-authored `data-ref` nodes; use [@bindTo](/docs/decorators/bind-to). Use `@onUpdated(...)` when a property change must run a procedure (focus, timers, joined ARIA). Reach for `onConnected()` only when first-connect setup genuinely needs the synchronized attributes or committed DOM, and must be repeated after a reconnect. `@query(...)` is unrelated: use it only when that setup needs a live element handle.
## Bindings And Reactive Reads
`RadiantElement` exposes three ways to wire reactive values into JSX:
- `this.value` for the raw value
- `this.bind('value')` for an explicit JSX binding
- `this.bindings.value` or `this.$.value` for property-style JSX bindings
Use the raw value in imperative code and render logic. Use bindings in stable JSX leaf positions such as text, attributes, `aria`, `data`, and boolean props. On hosts that do not override `render()`, use [@bindTo](/docs/decorators/bind-to) for the same leaf writes against existing DOM.
## Derived Bindings
Bindings expose the current reactive value, but sometimes JSX needs a **projection** of that value — a record lookup, an object key, or a transform.
### Member access in JSX
For object-like bindings, use member access directly in `render()`:
```tsx
render() {
return {this.$.config.label}
;
}
```
This is sugar for `this.$.config.map((config) => config.label)`. The runtime memoizes each key on the binding, so repeated reads in `render()` reuse the same derived binding identity.
Use this for simple object-key reads. You do not need a field initializer for `{this.$.config.label}` in normal components.
### `map` for transforms and lookups
Use `map` when the projection is not a plain property read — record lookups, computed strings, or method calls:
```tsx
private readonly themeLabel = this.$.preference.map((preference) => THEME_CONFIG[preference].label);
render() {
return Theme: {this.themeLabel}
;
}
```
**Create `map` results once** (field initializer or cached host field). Calling `.map(...)` inside `render()` creates a new binding on every pass, which breaks the live-subscription fast path.
```tsx
// Avoid — new derived binding every render
render() {
return {this.$.preference.map((p) => THEME_CONFIG[p].label)}
;
}
```
### Rules
- **Member access** (`this.$.config.label`) — fine inline in JSX for simple keys.
- **`map`** — hoist transforms and lookups to a create-once host field.
- **Object props are shallow** — projections update when the whole object is replaced (`this.config = { ... }`), not when nested keys are mutated in place.
- **Bracket lookups need `map`** — use `this.$.preference.map((p) => THEME_CONFIG[p].label)`, not `THEME_CONFIG[this.$.preference]`.
## Advanced Host Integration
Every reactive host member (`@state`, `@prop`, `@attr`, and `signal()`) is backed by a signals `State` inside the host. Decorator APIs are unchanged, but advanced integrations can work with that member registry directly:
- `createReactiveMember(name, initialValue)` — create and register host-owned member state
- `registerReactiveMember(name, signal)` — register externally owned state, such as a user `signal()`
- `getReactiveMember(name)` — read the member state registered for a property name
Use these when you build host adapters, tests, or custom decorators. Prefer `@state`, `@prop`, `@attr`, and `signal()` in application code.
**Removed in 0.3.0:** `trackReactiveRead(...)`, `registerReactiveDependencyReader(...)`, and the exported `ReactiveField` metadata type. Dependency tracking now flows through member `State.get()` and JSX bindings adapted from that state.
## When To Use RadiantElement
- Use `RadiantElement` when you want a custom element host, whether the host is imperative or JSX-owned.
- Override `render()` when the host itself should own a JSX view.
- Skip `render()` when the host should enhance authored light DOM instead.
- Override `onConnected()` for post-catch-up connect work; keep listeners in `connectedCallback`.
- In JSX-authored composite libraries, keep parent-owned children in the view shell; do not re-project them through `` (see [Slots — JSX-authored composites](/docs/components/slots#jsx-authored-composites)).
- Use [RadiantController](/docs/components/radiant-controller) when the DOM is authored elsewhere and a custom element wrapper would be unnecessary.
## See Also
- [RadiantController](/docs/components/radiant-controller)
- [Slots](/docs/components/slots)
- [Counter](/docs/examples/counter)
- [Hydration](/docs/ssr/hydration)