Elements & Attributes

The $ namespace is your entry point for creating DOM elements. Every element factory returns an Effect that produces a real DOM node — no virtual DOM, no diffing.

Creating Elements

import { $, collect } from "@effex/dom";

yield* $.div({ class: "container" }, collect(
  $.h1({}, $.of("Hello")),
  $.p({}, $.of("Welcome to Effex")),
));

Every HTML and SVG element has a corresponding factory on $: $.div, $.span, $.button, $.input, $.svg, $.path, and so on.

Children

Use $.of() to lift primitives and Readables into children, and collect() to combine multiple children:

// Single child
yield* $.h1({}, $.of("Hello World"));

// Multiple children
yield* $.div({}, collect(
  $.of("Hello"),
  $.span({}, $.of("World")),
));

// Empty child (renders nothing)
yield* $.div({}, $.empty);

$.of() accepts strings, numbers, Readables, and DOM nodes. When you pass a Readable, the text updates automatically when the value changes:

const count = yield* Signal.make(0);
yield* $.span({}, $.of(count));  // Updates in place when count changes

Template Strings

The t tagged template creates reactive strings from Readables:

import { t } from "@effex/dom";

const name = yield* Signal.make("World");
const count = yield* Signal.make(0);

// Creates a Readable<string> that updates automatically
yield* $.p({}, $.of(t`Hello, ${name}! Count: ${count}`));

Attributes

Elements accept an optional attributes object as the first argument. Attributes can be static or reactive:

$.input({
  // Standard attributes
  type: "text",
  placeholder: "Enter name",
  disabled: true,
  id: "name-input",

  // Reactive attributes — UI updates automatically
  value: name,             // Readable<string>
  class: className,        // Readable<string>
  hidden: isHidden,        // Readable<boolean>

  // Style as object or string
  style: { color: "red", fontSize: "16px" },

  // Class as string, array, or Readable
  class: ["btn", isActive.pipe(Readable.map(a => a ? "btn-active" : ""))],

  // Data and ARIA attributes
  "data-testid": "name",
  "aria-label": "Name input",
  role: "textbox",

  // Ref binding
  ref: inputRef,
});

When you pass a Readable as an attribute value, Effex sets up a subscription — the DOM attribute updates in place whenever the Readable emits a new value. No re-rendering, no diffing.

Event Handlers

Event handlers are functions that optionally return an Effect:

$.button({
  onClick: (e) => count.update((n) => n + 1),
  onKeyDown: (e) => {
    if (e.key === "Enter") return submit.run();
  },
  onSubmit: (e) => {
    e.preventDefault();
    return handleSubmit();
  },
});

If the handler returns an Effect, Effex runs it. If it returns void or undefined, nothing extra happens. This means simple handlers stay simple, and complex ones get the full power of Effect.

Supported events include onClick, onInput, onChange, onSubmit, onKeyDown, onKeyUp, onFocus, onBlur, onMouseDown, onMouseUp, onMouseEnter, onMouseLeave, onPointerDown, onPointerUp, onPointerMove, onScroll, onWheel, onDragStart, onDrag, onDragEnd, onDrop, onDragOver, onTouchStart, onTouchMove, onTouchEnd, onAnimationEnd, onTransitionEnd, and more.

SVG Elements

SVG elements are also available on $:

$.svg({ viewBox: "0 0 24 24", width: 24, height: 24 },
  $.path({ d: "M12 2L2 22h20L12 2z", fill: "currentColor" }),
);