Quick Start
Three examples, each building on the last. All runnable in the browser - no build step needed.
1. A Counter
Define a component, render it, update state with this.$():
define('my-counter', {
$({ count = 0 }) {
return [
mx.button({ onclick: _ => this.$({ count: count - 1 }) }, '−'),
mx.span({ class: 'count' }, count),
mx.button({ onclick: _ => this.$({ count: count + 1 }) }, '+')
];
}
});
document.getElementById('demo').render(mx('my-counter'));define() registers a custom element. $() receives props (merged with state). this.$({ count: count + 1 }) updates state and re-renders.
Learn more about components →
2. User Cards
A component that takes props, computes derived values, and renders conditionally:
define('user-card', {
$({ name, role = 'Member', avatar }) {
let initials = name.split(' ').map(w => w[0]).join('');
return mx.div({ class: 'card', style: 'display:flex;gap:12px;align-items:center;padding:16px;border:1px solid #333;margin:4px 0' },
avatar
? mx.img({ src: avatar, style: 'width:40px;height:40px;border-radius:50%' })
: mx.div({ style: 'width:40px;height:40px;border-radius:50%;background:#f0a828;display:flex;align-items:center;justify-content:center;font-weight:600;color:#111' }, initials),
mx.div(
mx.div({ style: 'font-weight:500' }, name),
mx.div({ style: 'font-size:13px;color:#888' }, role)
)
);
}
});
let container = document.getElementById('demo');
container.render(
mx('user-card', { name: 'Alice Chen', role: 'Engineer' }),
mx('user-card', { name: 'Bob Smith', role: 'Designer' }),
mx('user-card', { name: 'Carol Wu', role: 'PM' })
);Props with defaults (role = 'Member'), conditional rendering (avatar ? ... : ...), computed values (initials).
Learn more about elements & rendering →
3. A Dynamic List
No component needed - just a function that renders into a container:
let items = ['Learn mx.js', 'Build something', 'Ship it'];
function renderList(target) {
target.render(
mx.h3('Tasks (' + items.length + ')'),
mx.div({ style: 'display:flex;gap:8px;margin:8px 0' },
mx.input({ id: 'new-item', placeholder: 'New task...', style: 'flex:1;padding:6px' }),
mx.button({ onclick: _ => {
let input = target.querySelector('#new-item');
if (!input.value.trim()) return;
items.push(input.value);
input.value = '';
renderList(target);
}}, 'Add')
),
mx.ul(
...items.map((item, i) =>
mx.li({ style: 'display:flex;justify-content:space-between;padding:4px 0' },
mx.span(item),
mx.button({ onclick: _ => {
items.splice(i, 1);
renderList(target);
}, style: 'border:none;background:none;color:#ef4444;cursor:pointer' }, '\u00d7')
)
)
)
);
}
renderList(document.getElementById('demo'));Plain functions work great for simple UI. render() reconciles the list efficiently - only changed items update.
Learn more about lists, tables & patterns →
Next Steps
- Tutorial - build a contact form with toast notifications, step by step
- Forms - inputs, validation, multi-step wizards
- Architecture - routing, lazy loading, scaling to full applications