Radiant0.3.0-rc.2

@prop

@prop(...) declares a reactive property on a Radiant host.

On RadiantElement, use it for values that belong to the custom element's external API and may need attribute sync, type conversion, reflection, or JSX bindings.

On RadiantController, @prop(...) instead exposes a host property channel. That lets surrounding JS assign real values like objects or arrays directly on the attached host element without serializing them into attributes.

Example

import { RadiantElement, customElement, prop } from '@ecopages/radiant';
 
@customElement('user-card')
export class UserCard extends RadiantElement {
	@prop({ type: String }) declare name: string;
	@prop({ type: Number, defaultValue: 0 }) declare visits: number;
	@prop({ type: Boolean, reflect: true, defaultValue: false }) declare active: boolean;
}

SSR Property Staging

Before the first server render, assign properties through renderComponent (or on a disconnected host before calling renderRadiantElementHostToString):

import { renderComponentToString } from '@ecopages/radiant/server/render-component';
 
const html = await renderComponentToString(UserCard, {
	initialize: (element) => {
		element.visits = 7;
	},
});

Those staged values are serialized into the SSR host tag and used for the first render. This is the supported way to pass non-default props into SSR without attributes.

Controller Host Props

RadiantController can use the same decorator when the input should come from a real host property instead of markup.

import { RadiantController, controller, prop } from '@ecopages/radiant';
import { startControllers } from '@ecopages/radiant/controller-registry';
 
type ResultsListProps = {
	items: Array<{ id: string; label: string }>;
};
 
@controller('results-list')
export class ResultsListController extends RadiantController {
	@prop({ type: Array, defaultValue: [] }) declare items: ResultsListProps;
 
	override render() {
		return <ul>{this.items.map((item) => <li key={item.id}>{item.label}</li>)}</ul>;
	}
}
 
document.body.innerHTML = '<section data-controller="results-list"></section>';
 
const host = document.querySelector('[data-controller="results-list"]') as HTMLElement & ResultsListProps
 
host.items = [
	{ id: '1', label: 'Alpha' },
	{ id: '2', label: 'Beta' },
];
 
startControllers(document);

Prefer @ecopages/radiant/controller-registry for startControllers(...) when a module only needs controller activation.

Options

OptionTypeDescription
typeString | Number | Boolean | Object | ArrayAttribute conversion strategy.
reflectbooleanReflect property changes back to the host attribute.
attributestringOverride the attribute name.
defaultValueTDefault property value when the attribute is absent.
bindboolean | stringExpose a JSX binding companion such as $count or a custom binding name.

How It Works

@prop(...) uses the property reactivity machinery underneath:

  • reads the attribute into the declared property type
  • writes the property back through the converter when reflection is enabled
  • stores reactive metadata for SSR host serialization
  • notifies @onUpdated listeners when the member state changes
  • can expose a subscribable JSX binding companion

On RadiantController, the same decorator reads and writes a real property on the attached host element instead of going through attribute serialization.

That means this split is intentional:

  • use @attr(...) when the value should stay in markup
  • use @prop(...) when JS code should pass structured values directly

JSX Binding Defaults

When you omit bind, Radiant exposes companion bindings by default.

So this:

class CounterCard extends RadiantElement {
	@prop({ type: Number, defaultValue: 0 }) count!: number;
}

automatically gives you this.$.count and this.bindings.count.

When To Use It

  • Use @prop(...) for public custom-element API.
  • Use @prop(...) on RadiantController when surrounding JS should pass values through the host element as real properties.
  • Use @state for internal mutable component state.

See @state and @attr.