--- title: "JSX Overview" description: "What @ecopages/jsx provides and how it fits into the Radiant ecosystem." group: JSX order: 1 --- import { CodeTabs } from '@/components/code-tabs'; # JSX Overview `@ecopages/jsx` is the JSX authoring layer for the Radiant ecosystem. Use it when you want: - TSX syntax - typed intrinsic HTML and SVG elements - direct DOM mounting - server rendering and hydration helpers - fine-grained child bindings without a hook runtime `@ecopages/jsx` also gives you: - explicit DOM event bindings with `on:*` - native listener escape hatches with `on-native:*` - property bindings with `prop:*` - direct child bindings from reactive sources that expose `get()` and `subscribe(...)` Keep using `@ecopages/radiant` when you need custom-element classes, decorators, lifecycle, context, and host-managed reactivity. ## Package Boundary This is the key split: - `@ecopages/jsx` owns TSX syntax, template creation, DOM mounting, hydration, and SSR serialization. - `@ecopages/radiant` owns component behavior and decides when a host should render. - Both the DOM and SSR flows consume the same template result shape. ## Entrypoints Choose the narrowest import path for the environment you are writing in. Prefer subpaths in environment-specific modules so bundlers keep browser and server code separate. | Entrypoint | Use it for | | :-- | :-- | | `@ecopages/jsx` | Shared app code: JSX primitives, `createRoot(...)`, hydration helpers, and types | | `@ecopages/jsx/client` | Browser-only entry files (same mounting surface as the root barrel) | | `@ecopages/jsx/server` | Node or Bun SSR: `renderToString(...)`, custom-element render hooks, and SSR scope helpers | | `@ecopages/jsx/jsx-runtime` | Automatic JSX runtime wiring (`jsx`, `jsxs`, `Fragment`) | | `@ecopages/jsx/jsx-dev-runtime` | Development runtime when the toolchain emits `jsxDEV(...)` | ## Install And Configure `@ecopages/radiant` depends on `@ecopages/signals` directly, so installing it brings signals automatically. `@ecopages/jsx` declares `@ecopages/signals` as a peer dependency; install signals explicitly only when using `@ecopages/jsx` standalone with signal-backed bindings such as `mapSubscribable(...)`. ```json { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@ecopages/jsx" } } ``` Or enable it per file: ```tsx /** @jsxImportSource @ecopages/jsx */ ``` ## Mental Model The shortest accurate model is: 1. JSX produces a renderer-neutral template result. 2. That template result can go to the DOM renderer or the SSR renderer. 3. Fine-grained updates happen at bound child ranges, not through a hook scheduler. ## Empty Values And Removal Most code should use plain JavaScript values for empty output and removal semantics. - `null`, `undefined`, and `false` render no child content - `null` and `undefined` remove normal attributes - `false` removes boolean attributes such as `hidden` or `disabled` - `null` removes event handlers by leaving no next listener to attach - `undefined` clears property bindings by writing `undefined` ```tsx /** @jsxImportSource @ecopages/jsx */ return ( ); ``` Removing a binding this way follows the normal renderer path. If the next render changes the template shape, the DOM renderer may replace the affected node rather than preserve the previous instance. ## Quick Start With RadiantElement ```tsx /** @jsxImportSource @ecopages/jsx */ import { RadiantElement, customElement, prop } from '@ecopages/radiant'; @customElement('counter-card') export class CounterCard extends RadiantElement { @prop({ type: Number, reflect: true, defaultValue: 0 }) count!: number; private readonly increment = () => { this.count += 1; }; override render() { return (

Count: {this.count}

); } } ``` ## Derived Bindings (Radiant Hosts) On `RadiantElement` and `RadiantController`, `this.$` exposes stable `SubscribableJsxValue` bindings for reactive members. Skip this section if you only use plain `createRoot(...)` mounting. **Member access** — for object keys, use inline in JSX: ```tsx

{this.$.config.label}

``` The runtime memoizes each key on the binding, so this is identity-stable across renders. **`map`** — for transforms, record lookups, and non-property projections: ```tsx const themeLabel = this.$.preference.map((preference) => THEME_CONFIG[preference].label); ``` Hoist `.map(...)` to a create-once host field. Do not call `.map(...)` inside `render()` — each call creates a new derived binding. Derived bindings reuse the source `getValue` / `subscribe` contract and mount through the same reactive engines as any other binding. ## Next Pages - See [Authoring With JSX](/docs/jsx/authoring) for intrinsics, components, fragments, and attribute normalization. - See [JSX Custom Element Types](/docs/jsx/custom-element-types) for type augmentation and `attr:*` / `prop:*` defaults. - See [JSX Event Handling](/docs/jsx/events) for `on:*` and `on-native:*`. - See [JSX Client Rendering](/docs/jsx/rendering) for `createRoot(...)`, `hydrate(...)`, and container-level hydration. - See [JSX SSR](/docs/ssr/jsx-ssr) for `renderToString(...)` and SSR scope helpers. - See [Trusted Markup And Security](/docs/jsx/trusted-markup) for the escaping model and `unsafeHtml(...)`. - See [RadiantElement](/docs/components/radiant-element) for the host render lifecycle.