Forms & Data Binding
Because CellUI does not use a compiler (unlike Svelte's .svelte parser or Vue's v-model), we adhere to a strictly one-way data flow architecture.
You must explicitly wire input elements to their backing Signals.
One-Way Data Flow
To update a signal when a user types into an , you assign an oninput handler. To ensure the reflects the current value of the Signal, you bind the value attribute to it.
import { signal, view } from '@cmj/cellui';
function RegistrationForm() {
const username = signal('');
const password = signal('');
const submit = (e: Event) => {
e.preventDefault();
console.log("Registering:", username.value, password.value);
}
return view`
<form onsubmit="${submit}">
<label>
Username:
<input
type="text"
value="${username}"
oninput="${(e: Event) => username.value = (e.target as HTMLInputElement).value}"
/>
</label>
<label>
Password:
<input
type="password"
value="${password}"
oninput="${(e: Event) => password.value = (e.target as HTMLInputElement).value}"
/>
</label>
<button type="submit">Register</button>
<p>Live Preview: ${username}</p>
</form>
`;
}Why not Two-Way Data Binding?
While rewriting (e) => signal.value = e.target.value repeatedly can cause "boilerplate fatigue", it maintains CellUI's commitment to explicitness.
There is no hidden synchronization magic happening behind the scenes. The oninput function executes strictly inside standard Javascript rules.
Checkboxes and Radios
For booleans and enumerations, bind the native event slightly differently:
function Preferences() {
const receiveEmails = signal(false);
const theme = signal('light'); // 'light' | 'dark'
return view`
<fieldset>
<legend>Settings</legend>
<label>
<input
type="checkbox"
checked="${receiveEmails}"
onchange="${(e: Event) => receiveEmails.value = (e.target as HTMLInputElement).checked}"
/>
Subscribe to Newsletter
</label>
<div>
<label>
<input
type="radio"
name="theme"
value="light"
checked="${() => theme.value === 'light'}"
onchange="${() => theme.value = 'light'}"
/>
Light Mode
</label>
<label>
<input
type="radio"
name="theme"
value="dark"
checked="${() => theme.value === 'dark'}"
onchange="${() => theme.value = 'dark'}"
/>
Dark Mode
</label>
</div>
</fieldset>
`;
}Note: The snippet uses a function ${() => theme.value === 'dark'} for the checked boolean. Passing a function ensures CellUI re-evaluates the boolean whenever the dependent signal changes.