JSX Integration

Use familiar JSX syntax with DOM++ by providing a tiny custom factory function. This example runs entirely in the browser using Babel Standalone (no build step).

How it works

A /** @jsx d */ pragma tells Babel to transform <div> into d("div", props, ...children).

The d factory creates real DOM nodes and forwards special props (style, className, on) to the corresponding DOM++ setters.

All DOM++ reactive features (setState, setFineGrained + signals, reactive setChildren) work normally.

For production: use a bundler (Vite, Parcel, esbuild, etc.) and configure the JSX transform to use your factory instead of React.createElement.

Live Demo

JSX Source

This is the exact source that gets transformed by Babel and executed. The pragma, factory, and all four demo patterns are shown below.

/** @jsx d */

const app =
  document.getElementById("app");

// JSX factory: d(tag, props, ...children)
function d(tag, props, ...children) {
  const node = document.createElement(tag);

  if (props) {
    const { style, className, class: classAttr, on, ...attrs } = props;

    if (className || classAttr) {
      node.setAttributes({ class: className || classAttr });
    }

    if (style) {
      node.setStyles(style);
    }

    if (on) {
      node.setEvents(on);
    }

    if (Object.keys(attrs).length) {
      node.setAttributes(attrs);
    }
  }

  if (children.length) {
    node.setChildren(...children.flat());
  }

  return node;
}

function card(title, description, content) {
  return (
    <section className="card">
      <h2>{title}</h2>
      <small>{description}</small>
      {content}
    </section>
  );
}

function demoBasicUsage() {
  return card(
    "Basic Usage",
    "DOM++ extends real DOM nodes with chainable helpers.",
    (
      <div>
        <h3>Hello DOM++</h3>
        <p>This is a DOM++ element created via JSX.</p>
      </div>
    )
  );
}

function demoStatefulPanel() {
  const panel = <section className="panel" />;
  panel
    .setState({ total: 0 })
    .setChildren(({ state, setState }) => [
      <h3>{`Total: ${state.total}`}</h3>,
      <button on={{ click: () => setState(({ state }) => { state.total += 10000; }) }}>
        Donate 10k
      </button>
    ]);

  return card(
    "Stateful Panel",
    "State lives on the element; setChildren re-runs when state changes.",
    panel
  );
}

function demoCrossElement() {
  const summary = <div />;
  summary
    .setState({ total: 0 })
    .setChildren(({ state }) => [
      <strong>{`Total: ${state.total}`}</strong>
    ]);

  return card(
    "Cross-Element Updates",
    "Buttons update another element's state via its setState method.",
    (
      <div>
        {summary}
        <div className="actions">
          <button on={{ click: () => {
            summary.setState(({ state }) => { state.total += 10000; });
          } }}>Donate 10k</button>
          <button className="secondary" on={{ click: () => {
            summary.setState(({ state }) => { state.total += 25000; });
          } }}>Donate 25k</button>
        </div>
      </div>
    )
  );
}

function demoFineGrained() {
  const [count, setCount] =
    document.createSignal(0);

  const display =
    <strong className="signal-count" />;

  display
    .setFineGrained()
    .setText(() => `Signal: ${count()}`);

  return card(
    "Fine-Grained Signals",
    "JSX node + setFineGrained + signal-driven setText (no setState).",
    (
      <div>
        {display}
        <div className="actions">
          <button on={{ click: () => setCount( count() + 1 ) }}>+1</button>
          <button className="secondary" on={{ click: () => setCount(0) }}>Reset</button>
        </div>
      </div>
    )
  );
}

app.setChildren(
  demoBasicUsage(),
  demoStatefulPanel(),
  demoCrossElement(),
  demoFineGrained()
);
 
The runtime cost of Babel Standalone is only for this demo. In real apps the transform happens at build time and produces plain JS.