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
- Walks children left-to-right, reusing existing DOM nodes by tag name.
- If a child is a component (has
$), delegates rendering to that component. - Text nodes are updated in place when content changes.
- Excess old children are removed. New children are appended.
- Returns
this for chaining.
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():
| Syntax | Type | Behavior | Example |
|---|
name | Standard attribute | setAttribute(name, value). For checked, selected, value, also sets the property. | class: 'box' |
.name | Property | Directly sets el[name] = value. Bypasses attributes entirely. | '.value': 'text' |
name: null | Removal | removeAttribute(name). If the attribute is checked/selected/value, also clears the property. | disabled: null |
name: false | Removal | Same as null - removes the attribute. | hidden: false |
name: true | Boolean attribute | setAttribute(name, '') - sets the attribute with an empty string value. | disabled: true |
onname: fn | Event handler | Functions 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
| API | Returns | Purpose |
|---|
mx(tag, attrs?, ...children) | Array | Create element description for render() |
mx.tag(attrs?, ...children) | Array | Shorthand via Proxy destructuring |
dom(tag, attrs?, ...children) | HTMLElement | Create a real DOM element immediately |
define(name, { $, ... }) | void | Register a component tag name |
components | object | Global component registry |
el.render(...nodes) | this | Reconcile children into element |
el.$attrs(attrs) | this | Batch-set attributes/properties |
el.$(state) | this | Update component state and re-render |
el.$state | object | Component state (persistent across renders) |