Skip to main content

Framework Adapters

4 min read

Vanilla API

Vanilla adapter API reference. Three entry points – main package, JSX runtime, and htm binding.


Quick Reference

Main (@directive-run/el)

ExportTypeDescription
elFunctionCreate a typed DOM element
bindFunctionSubscribe element to system state
bindTextFunctionBind text content to system state
mountFunctionReplace children on state change
ElChildTypeUnion of valid child types

JSX Runtime (@directive-run/el/jsx-runtime)

ExportTypeDescription
jsxFunctionJSX automatic transform (production)
jsxsFunctionJSX with static children (production)
jsxDEVFunctionJSX automatic transform (development)
FragmentFunctionRenders children into a DocumentFragment
JSXNamespaceIntrinsicElements and Element types

htm (@directive-run/el/htm)

ExportTypeDescription
htmlTagged templatehtm bound to el()

el()

Create a typed DOM element with optional props and children.

function el<K extends keyof HTMLElementTagNameMap>(
  type: K,
  propsOrChild?: Partial<HTMLElementTagNameMap[K]> | ElChild,
  ...children: ElChild[]
): HTMLElementTagNameMap[K]

Parameters:

ParamTypeDescription
typeK extends keyof HTMLElementTagNameMapHTML tag name ("div", "span", "input", etc.)
propsOrChildPartial<HTMLElementTagNameMap[K]> | ElChildProps object or first child (auto-detected)
childrenElChild[]Additional children

Returns: HTMLElementTagNameMap[K] – the typed DOM element.

Props auto-detection: If the second argument is a string, number, boolean, null, undefined, Node, or array, it is treated as a child. Plain objects are treated as props.

// These are equivalent
el("p", {}, "Hello")
el("p", "Hello")

// Props detected as object
el("div", { className: "card" }, "content")

// Child detected as string
el("div", "content")

// Child detected as Node
el("div", el("span", "inner"))

// Child detected as array
el("ul", items.map(i => el("li", i)))

ElChild

type ElChild =
  | string      // → text node
  | number      // → text node (coerced via String())
  | boolean     // → silently skipped
  | null        // → silently skipped
  | undefined   // → silently skipped
  | Node        // → appended directly
  | ElChild[]   // → flattened recursively

bind()

Subscribe an element to a Directive system. The updater runs immediately with current state, then on every fact change.

function bind<E extends HTMLElement>(
  system: SingleModuleSystem<any>,
  element: E,
  updater: (
    el: E,
    facts: Record<string, unknown>,
    derived: Record<string, unknown>,
  ) => void,
): () => void

Parameters:

ParamTypeDescription
systemSingleModuleSystemA started Directive system
elementE extends HTMLElementThe element to bind
updater(el, facts, derived) => voidCalled on every state change

Returns: () => void – cleanup function that unsubscribes.

const span = el("span");

const cleanup = bind(system, span, (el, facts, derived) => {
  el.textContent = `${facts.count}`;
  el.className = derived.isHigh ? "high" : "low";
});

// Unsubscribe
cleanup();

bindText()

Shorthand for binding text content. Equivalent to bind() with el.textContent = selector(facts, derived).

function bindText(
  system: SingleModuleSystem<any>,
  element: HTMLElement,
  selector: (
    facts: Record<string, unknown>,
    derived: Record<string, unknown>,
  ) => string,
): () => void

Parameters:

ParamTypeDescription
systemSingleModuleSystemA started Directive system
elementHTMLElementThe element to bind
selector(facts, derived) => stringReturns the text to display

Returns: () => void – cleanup function that unsubscribes.

const label = el("span");

const cleanup = bindText(system, label, (facts, derived) => {
  return `${derived.doubled} items`;
});

mount()

Replace a container's children on every state change. Uses replaceChildren() for a single DOM operation per update.

function mount(
  system: SingleModuleSystem<any>,
  container: HTMLElement,
  renderer: (
    facts: Record<string, unknown>,
    derived: Record<string, unknown>,
  ) => Node | Node[],
): () => void

Parameters:

ParamTypeDescription
systemSingleModuleSystemA started Directive system
containerHTMLElementThe container whose children are replaced
renderer(facts, derived) => Node | Node[]Returns the new children

Returns: () => void – cleanup function that unsubscribes.

The renderer can return a single Node or an array of Nodes:

// Array of nodes
mount(system, listEl, (facts) => {
  return items.map(item => el("li", item));
});

// Single node
mount(system, container, (facts) => {
  return el("p", "Loading...");
});

JSX Runtime

The JSX runtime allows writing JSX that compiles to el() calls without React.

Setup

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@directive-run/el"
  }
}

jsx / jsxs / jsxDEV

function jsx(type: string, props: Record<string, unknown>): HTMLElement

These are called by the JSX compiler – you don't call them directly. They extract children from props and pass everything else to el().

Fragment

function Fragment(props: { children?: ElChild | ElChild[] }): DocumentFragment

Renders children into a DocumentFragment (no wrapper element):

const items = (
  <>
    <li>One</li>
    <li>Two</li>
  </>
);

JSX Namespace

namespace JSX {
  type IntrinsicElements = {
    [K in keyof HTMLElementTagNameMap]:
      Omit<Partial<HTMLElementTagNameMap[K]>, "children"> & {
        children?: ElChild | ElChild[];
      };
  };
  type Element = HTMLElement;
}

All HTML element props are available with full type inference. The children prop accepts ElChild values (strings, numbers, nodes, arrays, null/undefined/boolean).


htm

Tagged template binding using htm. Requires htm as a peer dependency.

html

import { html } from "@directive-run/el/htm";

const element = html`<div className="card">Hello</div>`;

html is htm.bind(el) – it parses the tagged template and calls el() for each element. Supports interpolation, nested elements, event handlers, and array children.


Peer Dependencies

PackageRequiredNotes
@directive-run/coreYesCore runtime
htmOptionalOnly for @directive-run/el/htm

Bundle Size

EntryESMCJS
@directive-run/el1.3 KB1.4 KB
@directive-run/el/jsx-runtime0.7 KB0.8 KB
@directive-run/el/htm0.6 KB0.7 KB

All sizes are minified with source maps. htm (700 bytes) is external.

Previous
Vanilla

Stay in the loop. Sign up for our newsletter.

We care about your data. We'll never share your email.

Powered by Directive. This signup uses a Directive module with facts, derivations, constraints, and resolvers – zero useState, zero useEffect. Read how it works

Directive - Constraint-Driven State Management for TypeScript