--- title: "@signal" description: "Declare host-aware writable signal fields on Radiant elements and components." group: Decorators order: 5 --- # @signal `@signal` declares a host-aware writable signal field. The decorated member becomes a real `WritableSignal` instance that JSX can consume directly in child or attribute positions. Use it when the field itself should behave like a signal object. If you only need ordinary reactive host state, use [@state](/docs/decorators/state) instead. ## Example ```tsx /** @jsxImportSource @ecopages/jsx */ import { RadiantElement, customElement } from '@ecopages/radiant'; import { signal } from '@ecopages/radiant/decorators/signal'; @customElement('signal-counter') export class SignalCounter extends RadiantElement { @signal count = 0; private readonly increment = () => { this.count.update((value) => value + 1); }; override render() { return (

Count: {this.count}

); } } ``` ## Options | Option | Type | Description | | :-- | :-- | :-- | | `bind` | `boolean \| string` | Exposes a JSX binding companion such as `$count` or a custom binding name | | `initial` | `T` | Optional initial value when the field does not provide one directly | | `source` | `WritableSignal \| (host) => WritableSignal` | Connects an existing writable signal instead of creating a host-owned one | | `hydrate` | `String \| Number \| Boolean \| Object \| Array` | Serializes the current signal value into SSR host output and restores it during hydration | ## What It Changes - the field becomes a real writable signal instance - connected signals still flow through Radiant's update callback channel - `@onUpdated(...)` and JSX bindings keep working with the same host update model - on render-owning `RadiantElement` hosts, signal and store reads performed during `render()` participate in rerender invalidation directly That last point is the main difference from `@state`: a render method can read the signal directly and let the signals runtime invalidate the view. ```tsx override render() { return

Count: {this.count}

; } ``` If the signal changes through `this.count.set(...)`, the rendered output updates without an extra imperative sync step. ## Shared Signals You can connect an existing signal instead of creating a host-owned one. ```ts import { State } from '@ecopages/signals'; import { RadiantElement, customElement } from '@ecopages/radiant'; import { signal } from '@ecopages/radiant/decorators/signal'; const sharedCount = new State(0); @customElement('shared-counter') export class SharedCounter extends RadiantElement { @signal({ source: sharedCount }) declare count: State; } ``` ## Relationship To @state - Use `@state` when the member should stay a plain host field managed by Radiant's reactive field system. - Use `@signal` when the member itself should be a writable signal value. ## Related Subpaths Signal primitives come from `@ecopages/signals`. If you need host-owned async state built on top of signals, use `createResource` from `@ecopages/radiant` (documented in [Resources](/docs/packages/signals-resources)). See [Signals Overview](/docs/packages/signals-overview) for the underlying signal model.