Direct atom binding — the new simplified API
import { html, atom, formAtom, isRequired, isEmail, renderApp } from 'mates';
import { Checkbox, FilledButton, InputField } from 'mates-ui';
const App = () => {
const email = formAtom("", [isRequired, isEmail]);
const age = atom(25);
const agree = formAtom(false, [(v) => v ? null : "Required"]);
return () => html`
<x-col gap="var(--md-space-3)" style="max-width:22.5rem;width:100%;">
${InputField({ atom: email, label: "Email", type: "email" })}
${InputField({ atom: age, label: "Age", type: "number" })}
${Checkbox({ atom: agree, label: "I agree to terms" })}
${FilledButton("Validate all", { onClick: () => { email.validate(); agree.validate(); } })}
</x-col>
`;
};
renderApp(App, document.getElementById('app'));
bindFormInputField — string
import { html, formAtom, isRequired, isEmail, renderApp } from 'mates';
import { InputField, bindFormInputField } from 'mates-ui';
const App = () => {
const email = formAtom("", [isRequired, isEmail]);
return () => html`
<div style="max-width:22.5rem;width:100%;">
${InputField(bindFormInputField(email, { label: "Email", type: "email", autocomplete: "email" }))}
</div>
`;
};
renderApp(App, document.getElementById('app'));
bindFormInputField — password rules
import { html, formAtom, isRequired, minLength, isPattern, renderApp } from 'mates';
import { InputField, bindFormInputField } from 'mates-ui';
const App = () => {
const password = formAtom("", [isRequired, minLength(8), isPattern(/[0-9]/, "Include a number")]);
return () => html`
<div style="max-width:22.5rem;width:100%;">
${InputField(bindFormInputField(password, { label: "Password", type: "password" }))}
</div>
`;
};
renderApp(App, document.getElementById('app'));
bindFormNumberInputField
import { html, formAtom, isRequired, renderApp } from 'mates';
import { InputField, bindFormNumberInputField } from 'mates-ui';
const App = () => {
const age = formAtom(18, [isRequired, (v) => v >= 13 && v <= 120 ? null : "Age must be 13–120"]);
return () => html`
<div style="max-width:12.5rem;width:100%;">
${InputField(bindFormNumberInputField(age, { label: "Age" }))}
</div>
`;
};
renderApp(App, document.getElementById('app'));
bindFormTextarea
import { html, formAtom, renderApp } from 'mates';
import { Textarea, bindFormTextarea } from 'mates-ui';
const App = () => {
const bio = formAtom("", [(v) => v.length <= 120 ? null : "Max 120 characters"]);
return () => html`
<div style="max-width:26.25rem;width:100%;">
${Textarea(bindFormTextarea(bio, { label: "Bio", rows: 3, maxLength: 120, showCount: true, helperText: "Validators run on each change." }))}
</div>
`;
};
renderApp(App, document.getElementById('app'));
bindFormCheckbox
import { html, formAtom, renderApp } from 'mates';
import { Checkbox, bindFormCheckbox } from 'mates-ui';
const App = () => {
const agree = formAtom(false, [(v) => v ? null : "Turn this on to continue"]);
return () => html`
<x-col gap="var(--md-space-2)">
${Checkbox(bindFormCheckbox(agree, { label: "I agree to the terms (required)" }))}
</x-col>
`;
};
renderApp(App, document.getElementById('app'));
bindFormSwitch
import { html, formAtom, renderApp } from 'mates';
import { Switch, bindFormSwitch } from 'mates-ui';
const App = () => {
const marketing = formAtom(true, []);
return () => html`
<x-col gap="var(--md-space-2)">
${Switch(bindFormSwitch(marketing, { label: "Marketing emails" }))}
</x-col>
`;
};
renderApp(App, document.getElementById('app'));
bindFormSlider
import { html, formAtom, renderApp } from 'mates';
import { Slider, bindFormSlider } from 'mates-ui';
const App = () => {
const volume = formAtom(40, [(v) => v >= 30 && v <= 80 ? null : "Must be between 30 and 80"]);
return () => html`
<div style="max-width:20rem;width:100%;">
${Slider(bindFormSlider(volume, { label: "Volume", showValue: true, format: (v) => v + "%", min: 0, max: 100, labelSuffixErrors: true }))}
</div>
`;
};
renderApp(App, document.getElementById('app'));
validateAll + .get()
import { html, formAtom, validateAll, isRequired, isEmail, minLength, renderApp } from 'mates';
import { FilledButton, InputField, bindFormInputField } from 'mates-ui';
const App = () => {
const email = formAtom("", [isRequired, isEmail]);
const password = formAtom("", [isRequired, minLength(8)]);
const shape = { email, password };
const result = { val: "" };
return () => html`
<x-col gap="var(--md-space-4)" style="max-width:24rem;width:100%;">
${InputField(bindFormInputField(email, { label: "Email", type: "email" }))}
${InputField(bindFormInputField(password, { label: "Password", type: "password" }))}
${FilledButton("Validate", {
onClick: () => {
const r = validateAll(shape);
result.val = r.isValid ? "Valid! email=" + email.get() : "Errors: " + JSON.stringify(r.errors);
email.validate(); password.validate();
},
})}
</x-col>
`;
};
renderApp(App, document.getElementById('app'));
Debounced text (draft vs committed)
import { html, minLength, isPattern, renderApp } from 'mates';
import { InputField, FilledButton, OutlinedButton, Text, debouncedFormString, bindDebouncedDraftInputField, resetDebouncedString } from 'mates-ui';
const App = () => {
const pair = debouncedFormString("", 400, [minLength(2), isPattern(/^[a-z0-9-]*$/i, "Letters, numbers, hyphens only")]);
return () => html`
<x-col gap="var(--md-space-3)" style="max-width:25rem;width:100%;">
${InputField(bindDebouncedDraftInputField(pair, { label: "Search slug", placeholder: "e.g. my-api" }))}
${Text("Draft: " + JSON.stringify(pair.draft()), { type: "body-sm", color: "muted" })}
${Text("Committed: " + JSON.stringify(pair.committed()), { type: "body-sm", color: "muted" })}
<x-row gap="var(--md-space-2)">
${FilledButton("Flush + validate", { size: "sm", onClick: () => { pair.flush(); pair.committed.validate(); } })}
${OutlinedButton("Reset", { size: "sm", onClick: () => resetDebouncedString(pair, "") })}
</x-row>
</x-col>
`;
};
renderApp(App, document.getElementById('app'));
Debounced slider
import { html, renderApp } from 'mates';
import { Slider, FilledButton, OutlinedButton, Text, debouncedFormNumber, bindDebouncedLiveSlider, resetDebouncedNumber } from 'mates-ui';
const App = () => {
const pair = debouncedFormNumber(50, 350, [(v) => v >= 40 && v <= 60 ? null : "Must be 40–60"]);
return () => html`
<x-col gap="var(--md-space-3)" style="max-width:22.5rem;width:100%;">
${Slider(bindDebouncedLiveSlider(pair, { showValue: true, format: (v) => String(v), min: 0, max: 100, label: "Level" }))}
${Text("Live: " + pair.live(), { type: "body-sm", color: "muted" })}
${Text("Committed: " + pair.committed(), { type: "body-sm", color: "muted" })}
<x-row gap="var(--md-space-2)">
${FilledButton("Flush", { size: "sm", onClick: () => { pair.flush(); pair.committed.validate(); } })}
${OutlinedButton("Reset to 50", { size: "sm", onClick: () => resetDebouncedNumber(pair, 50) })}
</x-row>
</x-col>
`;
};
renderApp(App, document.getElementById('app'));
Programmatic .set() / .get()
import { html, formAtom, isRequired, isEmail, renderApp } from 'mates';
import { InputField, OutlinedButton, bindFormInputField } from 'mates-ui';
const App = () => {
const email = formAtom("", [isRequired, isEmail]);
const age = formAtom(18, [isRequired]);
return () => html`
<x-col gap="var(--md-space-4)" style="max-width:24rem;width:100%;">
${InputField(bindFormInputField(email, { label: "Email", type: "email" }))}
<x-row gap="var(--md-space-2)" wrap>
${OutlinedButton("Prefill email", { size: "sm", onClick: () => email.set("hello@example.com") })}
${OutlinedButton("Clear email", { size: "sm", onClick: () => email.set("") })}
${OutlinedButton("Set age to 99", { size: "sm", onClick: () => age.set(99) })}
</x-row>
</x-col>
`;
};
renderApp(App, document.getElementById('app'));