Checkboxes & Radio Buttons
Checkboxes and radios use .checked property for state. Group radios with the same name.
let { form, label, input, button, div, h3 } = mx;
let agreed = false;
let payment = "credit";
function agreementForm(){
return form({
oninput(e){
if (e.target.name === "agree") agreed = e.target.checked;
if (e.target.name === "payment") payment = e.target.value;
demo.render(agreementForm());
},
onsubmit(e){
e.preventDefault();
alert(`Agreed: ${agreed}, Payment: ${payment}`);
}
},
h3("Terms Agreement"),
label(input({ type:"checkbox", name:"agree", ".checked": agreed }), " I agree to terms"),
h3("Payment Method"),
label(input({ type:"radio", name:"payment", value:"credit", ".checked": payment === "credit" }), " Credit Card"),
label(input({ type:"radio", name:"payment", value:"paypal", ".checked": payment === "paypal" }), " PayPal"),
button({ type:"submit" }, "Continue")
);
}
let demo = dom.div();
demo.render(agreementForm());
document.body.render(demo);Select Lists
Select elements use .value for the selected value and .selectedIndex for index.
let { form, label, select, option, button, div, p } = mx;
let country = "us";
function countryForm(){
return form({
oninput(e){
if (e.target.name === "country") {
country = e.target.value;
selectDemo.render(countryForm());
}
},
onsubmit(e){
e.preventDefault();
alert(`Selected country: ${country}`);
}
},
label("Country"),
select({ name:"country", ".value": country },
option({ value:"us" }, "United States"),
option({ value:"ca" }, "Canada"),
option({ value:"uk" }, "United Kingdom"),
option({ value:"de" }, "Germany")
),
p(`Selected: ${country.toUpperCase()}`),
button({ type:"submit" }, "Submit")
);
}
let selectDemo = dom.div();
selectDemo.render(countryForm());
document.body.render(selectDemo);Forms - multi-step wizard
Multi-step forms keep all state in a single object. Each step renders different fields, and navigation buttons move between them. The entire wizard re-renders on every state change.
let { div, h3, label, input, button, span, p } = mx;
let step = 0;
let data = { name: "", email: "", plan: "free" };
let host = dom.div();
function steps() { return ["Account", "Plan", "Confirm"]; }
function Wizard() {
let names = steps();
return div(
// Step indicator
div({ class: "steps" },
...names.map((s, i) =>
span({
class: i === step ? "active"
: i < step ? "done" : ""
},
span({ class: "num" }, i < step ? "✓" : i + 1),
s
)
)
),
// Step content
step === 0 ? div(
label("Name"),
input({ name: "name", type: "text", ".value": data.name,
oninput(e) { data.name = e.target.value; }
}),
label("Email"),
input({ name: "email", type: "email", ".value": data.email,
oninput(e) { data.email = e.target.value; }
})
) : step === 1 ? div(
h3("Choose a plan"),
label(
input({ type: "radio", name: "plan", value: "free",
".checked": data.plan === "free",
oninput() { data.plan = "free"; }
}), " Free"
),
label(
input({ type: "radio", name: "plan", value: "pro",
".checked": data.plan === "pro",
oninput() { data.plan = "pro"; }
}), " Pro ($9/mo)"
)
) : div(
h3("Confirm"),
p("Name: " + data.name),
p("Email: " + data.email),
p("Plan: " + data.plan)
),
// Navigation
div({ class: "wizard-nav" },
step > 0 ? button({
onclick() { step--; host.render(Wizard()); }
}, "Back") : null,
step < 2 ? button({
onclick() { step++; host.render(Wizard()); }
}, "Continue") : button({
onclick() { alert("Done! " + JSON.stringify(data)); }
}, "Submit")
)
);
}
host.render(Wizard());
document.body.render(host);Wizard pattern
Keep all form data in one object and a step counter. Each step is a ternary branch that renders different fields. Navigation buttons increment/decrement the step and re-render. The same pattern scales to any number of steps.