API Reference

Complete reference for every function and method exposed by mx.js. The library exposes four globals: mx, dom, define, and components.

mx(tag, attrs?, ...children)

Creates a lightweight element description (an array with ._=true). This is not a DOM node - it's a compact representation that render() will reconcile against the live DOM.

mx is a Proxy. You can call it directly or destructure tag helpers from it:

// Direct call
mx('div', { class: 'box' }, 'Hello');

// Proxy destructuring (most common)
let { div, span, button, h1 } = mx;
div({ class: 'box' }, span('Hello'));

// CamelCase auto-converts to kebab-case
mx.myComponent({ prop: 'value' });
// equivalent to: mx('my-component', { prop: 'value' })

Signature

mx(tagName: string, attrs?: object, ...children) → Array

If the first argument after the tag is not a plain object (or has a tagName property), it is treated as a child, not attributes.

dom(tag, attrs?, ...children)

Creates and returns an actual DOM element immediately. Same Proxy/destructuring pattern as mx, but produces live nodes instead of descriptions.

// Direct call - returns a real DOM element
let el = dom('div', { class: 'box' }, 'Hello');
document.body.appendChild(el);

// Proxy destructuring
let { div, input } = dom;
let field = input({ type: 'text', '.value': 'hello' });
document.body.appendChild(field);

// With components - calls $() and returns the element
let btn = dom('a-button', { label: 'Click me' });

Signature

dom(tagName: string, attrs?: object, ...children) → HTMLElement

For component elements (registered via define()), dom() calls element.$(attrs) and returns the element. For plain elements, it calls $attrs() and render().

When to use dom() vs mx()

Use mx() inside render() calls for efficient reconciliation. Use dom() when you need a real element immediately (e.g., to store a reference, call methods on it, or pass to third-party code).

define(name, methods)

Registers a component tag name. When mx creates an element with that tag, it copies the methods onto the element instance. The special $ method is the render function.

define('my-counter', {
  $({ count = 0 }) {
    let { div, button, span } = mx;
    let c = (this.$state.count ??= count);
    return div(
      button({ onclick: () => this.$({ count: c + 1 }) }, '+'),
      span(c)
    );
  }
});

// Usage with mx()
document.body.render(
  mx.myCounter({ count: 5 })
);

// Usage with dom()
let counter = dom('my-counter', { count: 5 });

The $ function

The $ method is the core of every component. It receives a state object (merged from attributes and this.$state) and returns an array of children to render.

define('greeting', {
  $({ name = 'World' }) {
    // this = the DOM element
    // this.$state = persistent state across renders
    // Return children (array or single mx array)
    return mx.div(
      mx.h2('Hello, ' + name),
      mx.p('Welcome to mx.js')
    );
  }
});

What $ receives

When called via render(), the state argument is the attributes object from mx('tag', attrs), merged into this.$state via Object.assign.

When called directly as element.$(newState), the argument is merged the same way.

this.$state

A plain object that persists across renders. Because $ receives Object.assign(this.$state, props), parent props overwrite same-named keys on every render. Use ??= to initialize a value once and protect it from being overwritten:

$({ count = 0 }) {
  // First render: $state.count is undefined, so ??= sets it from the prop
  // Later renders: $state.count already exists, ??= skips the prop
  let c = (this.$state.count ??= count);
}

this.$attrs(attrs)

Efficiently sets multiple attributes/properties on the element. Supports the same prefix system as render(). Returns this for chaining.

let el = dom.input();
el.$attrs({
  type: 'text',
  '.value': 'hello',
  class: 'form-input',
  placeholder: 'Type here...'
});

this.render(...children)

Available on every element (added to Element.prototype). Inside components, the wrapper calls render() with whatever $ returns.

Element.prototype.render(...nodes)

The core reconciliation method. Accepts any mix of mx arrays (._=true), DOM nodes, strings, and numbers. Falsy values (null, undefined, false) are skipped.

let { div, span } = mx;
let container = document.getElementById('app');

// Initial render
container.render(
  div({ class: 'box' }, span('Hello')),
  div({ class: 'box' }, span('World'))
);

// Update - mx diffs and patches minimally
container.render(
  div({ class: 'box active' }, span('Hello!')),
  div({ class: 'box' }, span('World'))
);

Behavior

Signature

Element.prototype.render(...nodes: Array<MxArray | Node | string | number | null>) → this

Element.prototype.$attrs(attrs)

Sets attributes and properties on an element using the same prefix system as render(). Returns this for chaining.

el.$attrs({
  class: 'active',           // setAttribute('class', 'active')
  '.value': 'hello',         // el.value = 'hello'
  '.checked': true,          // el.checked = true
  onclick: fn,               // el.onclick = fn (functions are assigned as properties)
  disabled: null,             // el.removeAttribute('disabled')
  hidden: false               // el.removeAttribute('hidden')
});

Attribute Prefix Reference

The attribute system recognizes these patterns when setting attributes in both render() and $attrs():

SyntaxTypeBehaviorExample
nameStandard attributesetAttribute(name, value). For checked, selected, value, also sets the property.class: 'box'
.namePropertyDirectly sets el[name] = value. Bypasses attributes entirely.'.value': 'text'
name: nullRemovalremoveAttribute(name). If the attribute is checked/selected/value, also clears the property.disabled: null
name: falseRemovalSame as null - removes the attribute.hidden: false
name: trueBoolean attributesetAttribute(name, '') - sets the attribute with an empty string value.disabled: true
onname: fnEvent handlerFunctions are assigned as properties: el[name] = fn. Cleaned up automatically when the handler changes or is removed.onclick: fn

Event handler cleanup

mx tracks event handlers with a stamp system. When a handler function changes between renders, the old one is replaced. When a handler is no longer present in attrs, it is set to null automatically.

Special attributes

Three attributes - checked, selected, and value - are treated specially. When set via setAttribute(), the corresponding DOM property is also set. When removed, the property is cleared to ''. For most cases, prefer the .property prefix for these.

components

A plain object that serves as the global component registry. When you call define(name, methods), the methods are stored in components[name]. You can check if a component is registered:

if (!components['my-widget']) {
  define('my-widget', { $() { ... } });
}

Quick Reference

APIReturnsPurpose
mx(tag, attrs?, ...children)ArrayCreate element description for render()
mx.tag(attrs?, ...children)ArrayShorthand via Proxy destructuring
dom(tag, attrs?, ...children)HTMLElementCreate a real DOM element immediately
define(name, { $, ... })voidRegister a component tag name
componentsobjectGlobal component registry
el.render(...nodes)thisReconcile children into element
el.$attrs(attrs)thisBatch-set attributes/properties
el.$(state)thisUpdate component state and re-render
el.$stateobjectComponent state (persistent across renders)