Forms
wybthon.forms¶
forms
¶
Form state, validation helpers, and accessibility attribute utilities.
This module gives you a small but complete toolkit for building controlled form components on top of Wybthon's reactive primitives:
form_statecreates a map ofFieldentries (each backed by signals for value, error, and touched flags).bind_text,bind_checkbox, andbind_selectreturn prop dictionaries you can spread onto inputs.on_submitandon_submit_validatedwrap submit handlers with the rightpreventDefault/ validation glue.- Validator helpers (
required,min_length,max_length,email) compose withvalidate,validate_field, andvalidate_form. a11y_control_attrsanderror_message_attrsgenerate ARIA attributes (reactively) so error messages are announced correctly.
Example
form = form_state({"name": "", "email": ""})
rules = {"name": [required()], "email": [required(), email()]}
def save(fields):
print({k: f.value() for k, f in fields.items()})
form(
input_(**bind_text(form["name"], validators=rules["name"])),
span(form["name"].error, **error_message_attrs(id="name-error")),
button("Save", type="submit"),
on_submit=on_submit_validated(rules, save, form),
)
See Also
Type Aliases:
| Name | Description |
|---|---|
AsyncValidator |
A validator takes a value and returns an error message, or |
Classes:
| Name | Description |
|---|---|
Field |
Reactive state for one form field: value, error, and touched. |
FormState |
Fields plus aggregate dirty, validating, and submission state. |
Functions:
| Name | Description |
|---|---|
required |
Validate that a value is present and non-empty. |
min_length |
Validate that the stringified value length is at least |
max_length |
Validate that the stringified value length is at most |
email |
Validate a basic email address format with a lightweight regex. |
validate |
Return the first validation error, or |
form_state |
Create fields and aggregate form state from initial values. |
bind_text |
Bind a text input to a field with validation on every |
bind_checkbox |
Bind a checkbox input to a boolean field ( |
bind_select |
Bind a |
bind_number |
Bind a numeric input, preserving an empty value as None. |
bind_multiselect |
Bind all selected option values without per-option bridge reads. |
on_submit |
Create a submit handler that prevents default and forwards to |
validate_field |
Validate a single field and update its |
validate_form |
Validate every field in a form against a rules map. |
on_submit_validated |
Submit handler that validates the whole form before calling |
rules_from_schema |
Build a validators map from a small declarative schema. |
a11y_control_attrs |
Reactive ARIA attributes for a control bound to a field. |
error_message_attrs |
Attributes for an accessible error-message container (a polite live region). |
AsyncValidator
¶
A validator takes a value and returns an error message, or None when valid.
Field
¶
Reactive state for one form field: value, error, and touched.
Attributes:
| Name | Type | Description |
|---|---|---|
value |
Signal[T]
|
Accessor for the current input value. |
set_value |
T
|
Setter for the value. |
error |
Accessor[str | None]
|
Accessor for the latest validation error, or |
set_error |
Setter[str | None]
|
Setter for the error. |
touched |
Accessor[bool]
|
Accessor that's |
set_touched |
Setter[bool]
|
Setter for the touched flag. |
Methods:
| Name | Description |
|---|---|
validate |
Validate the current value, mark the field touched, and store the error. |
set_value |
Stage a value and invalidate any validation for an earlier edit. |
reset |
Reset value, errors, and interaction state; optionally set a new baseline. |
validate_async |
Validate the latest edit; stale responses never overwrite newer state. |
validate
¶
Validate the current value, mark the field touched, and store the error.
set_value
¶
set_value(value: T | Callable[[T], T]) -> T
Stage a value and invalidate any validation for an earlier edit.
reset
¶
reset(value: Any = _MISSING) -> None
Reset value, errors, and interaction state; optionally set a new baseline.
validate_async
async
¶
validate_async(validators: list[AsyncValidator]) -> str | None
Validate the latest edit; stale responses never overwrite newer state.
FormState
¶
Fields plus aggregate dirty, validating, and submission state.
Methods:
| Name | Description |
|---|---|
data |
Read the current values as a detached mapping. |
reset |
Reset all fields, optionally replacing their initial values. |
submit |
Validate and submit a value snapshot, exposing pending and failure state. |
reset
¶
Reset all fields, optionally replacing their initial values.
required
¶
required(message: str = 'This field is required') -> Validator
Validate that a value is present and non-empty.
Returns a validator that returns message for None or blank
strings (after strip()), otherwise None.
min_length
¶
Validate that the stringified value length is at least n.
max_length
¶
Validate that the stringified value length is at most n.
email
¶
email(message: str = 'Invalid email address') -> Validator
Validate a basic email address format with a lightweight regex.
Accepts None and empty strings as valid so it can be combined
with required, which handles the missing
case explicitly.
validate
¶
Return the first validation error, or None when all validators pass.
form_state
¶
Create fields and aggregate form state from initial values.
bind_text
¶
bind_checkbox
¶
Bind a checkbox input to a boolean field (checked + on_change).
bind_select
¶
Bind a <select> element to a field (value + on_change).
bind_number
¶
bind_number(field: Field[float | None], *, validators: list[Validator] | None = None) -> dict[str, Any]
Bind a numeric input, preserving an empty value as None.
bind_multiselect
¶
Bind all selected option values without per-option bridge reads.
on_submit
¶
on_submit(handler: Callable[[dict[str, Field[Any]]], Any], form: dict[str, Field[Any]]) -> Callable[[Any], Any]
Create a submit handler that prevents default and forwards to handler.
validate_field
¶
Validate a single field and update its error and touched signals.
validate_form
¶
on_submit_validated
¶
on_submit_validated(rules: dict[str, list[Validator]], handler: Callable[[dict[str, Field[Any]]], Any], form: dict[str, Field[Any]]) -> Callable[[Any], Any]
Submit handler that validates the whole form before calling handler.
rules_from_schema
¶
Build a validators map from a small declarative schema.
Supported per-field keys:
required:boolorstr(a string is used as the message).min_length:int, with optionalmin_length_message.max_length:int, with optionalmax_length_message.email:boolorstr(a string is used as the message).
a11y_control_attrs
¶
Reactive ARIA attributes for a control bound to a field.
aria-invalidis"true"while the field has an error.aria-describedbyreferencesdescribed_by_idwhile an error is present, so screen readers announce the message.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Props to spread onto the control (reactive values). |
What's in this module¶
A small toolkit for controlled forms on top of signals: per-field state,
binding helpers that return prop dicts to spread onto inputs, composable
validators, submit wrappers, and reactive ARIA attributes. Import it
from wybthon.forms; these names aren't re-exported from wybthon.
| Name | Description |
|---|---|
Field |
Reactive state for one field: value/set_value, error/set_error, touched/set_touched, and .validate(validators). |
form_state |
{name: initial} to a FormState. |
FormState |
A {name: Field} dict with aggregate dirty, validating, submitting, and submit_error state, plus data(), reset(), and await submit(handler, rules=...). |
bind_text |
{"value": accessor, "on_input": handler} for text inputs, validating on every input event. |
bind_number |
Numeric input binding that keeps an empty value as None. |
bind_checkbox |
{"checked": accessor, "on_change": handler} for a boolean field. |
bind_select |
{"value": accessor, "on_change": handler} for <select>. |
bind_multiselect |
Binds every selected option of a <select multiple> to a list field. |
on_submit |
Submit handler that prevents default and calls handler(form). |
on_submit_validated |
Same, but validates the whole form against rules first. |
Validator, AsyncValidator |
Type aliases: (value) -> str | None, and the same returning an awaitable. |
required, min_length, max_length, email |
Validator factories with optional custom messages. |
validate, validate_field, validate_form |
Run validators on a value, a field, or a whole form. |
rules_from_schema |
Build a rules map from {"name": {"required": True, "min_length": 2}, ...}. |
a11y_control_attrs |
Reactive aria_invalid and aria_describedby for a control. |
error_message_attrs |
id, role="alert", and aria_live="polite" for the message container. |
from wybthon import button, component, form, input_, label, span
from wybthon.forms import (
a11y_control_attrs,
bind_checkbox,
bind_text,
email,
error_message_attrs,
form_state,
on_submit_validated,
required,
)
@component
def Signup():
fields = form_state({"name": "", "email": "", "agree": False})
rules = {"name": [required()], "email": [required(), email()]}
def save(f):
print({k: field.value.peek() for k, field in f.items()})
return form(
label("Name", html_for="name"),
input_(
id="name",
**bind_text(fields["name"], validators=rules["name"]),
**a11y_control_attrs(fields["name"], described_by_id="name-error"),
),
span(fields["name"].error, **error_message_attrs(id="name-error")),
label(input_(type="checkbox", **bind_checkbox(fields["agree"])), " I agree"),
button("Save", type="submit"),
on_submit=on_submit_validated(rules, save, fields),
)
- A field's
erroraccessor isNonewhile valid, so embedding it as a child renders nothing until there's a message. touchedbecomesTrueon the first input, so you can delay showing errors:lambda: fields["name"].error() if fields["name"].touched() else None.email()accepts empty values; combine it withrequired().- In CPython tests, drive a form with
wybthon.testing:fire.input(screen.get_by_label_text("Name"), "Ada"), thenfire.submit(form_node).
See also¶
- Events: the
DomEventthese handlers receive - HTML helpers: why
valueandcheckedare DOM properties - Concepts: Forms
- Examples: Forms