Forms¶
The form classes provide the declarative base for field definitions, the validation pipeline, and the data management API.
BaseForm¶
BaseForm
¶
Base class for form definitions.
Handles field binding, attribute access, validation, and data management.
clean
¶
Full form-level cleaning pipeline.
Calls validate() to check each field. If all pass, calls
clean_form() for cross-field checks. Any calls to
add_error() inside clean_form() also cause clean() to
return False, even if clean_form() returns True.
After clean_form() completes, all field widgets are notified so
that any errors set via the backward-compatible direct-assignment
style are reflected in the UI.
clean_form
¶
Override for cross-field validation.
Called only after all individual fields pass validate().
Return True if valid, False otherwise.
Use self.add_error(field_name, message) to attach errors to
specific fields — this is preferred over direct attribute assignment.
add_error
¶
Attach a cross-field error to a named field.
Intended for use inside clean_form(). The error is visible in
the field's error label when the UI is next refreshed (which happens
automatically at the end of clean()).
Raises FormError if field_name does not exist.
layout
¶
Return the widget that renders this form.
With no argument, returns a :class:~textual_wtf.DefaultFormLayout widget
(fields + Submit/Cancel buttons). The default may be customised by setting
the :attr:layout_class class attribute on the form.
With a :class:~textual_wtf.FormLayout subclass, returns an instance of
that class wrapping this form.
With a callable, calls using(form) and returns the result.
The callable receives this form instance and must return a
:class:~textual.widget.Widget.
Usage::
# default layout
yield self.form.layout()
# custom Widget subclass
yield self.form.layout(MyTwoColumnLayout)
# callable returning a Widget
def my_layout(form) -> Widget:
return MyTwoColumnLayout(form=form)
yield self.form.layout(my_layout)
Form¶
Form
¶
Class attribute reference¶
layout_class¶
The layout class used by layout(). When None, DefaultFormLayout is used. Set this on your form class to use a custom layout by default:
label_style¶
Default label positioning for all fields in this form. Fields that explicitly set their own label_style are not affected. Valid values:
"above"— label on its own line above the input"beside"— label to the left of the input on the same line"placeholder"— label text used as the input placeholder; no visible label
help_style¶
Default help-text rendering for all fields. Valid values:
"below"— help text appears as a muted line below the input"tooltip"— help text appears in a tooltip on hover / focus
required¶
Form-wide required default. None means "do not override field defaults". See the required cascade.
title¶
Displayed as a bold heading at the top of DefaultFormLayout. Also used as the tab label in TabbedForm.
Messages¶
Form.Submitted¶
Posted when the user confirms the form and all validation passes.
@dataclass
class Submitted(Message):
layout: FormLayout # the FormLayout widget that received the submit action
form: BaseForm # the form instance with validated data
Handle it anywhere in the widget hierarchy above the layout:
@on(MyForm.Submitted)
def handle_submit(self, event: MyForm.Submitted) -> None:
data = event.form.get_data()
Form.Cancelled¶
Posted when the user presses Cancel or the Escape key.
Method reference¶
init¶
def __init__(
self,
data: dict[str, Any] | None = None,
*,
label_style: LabelStyle | None = None,
help_style: HelpStyle | None = None,
required: bool | None = None,
title: str | None = None,
) -> None
All keyword arguments override the corresponding class attribute for this instance only.
validate / is_valid¶
Run all validators on every field. Returns True only if every field passes. Updates error state on failing BoundField objects. Does not call clean_form().
clean¶
Full pipeline: validate() then clean_form() (if all fields pass). Returns True only if both succeed. Synchronises error state to the UI after clean_form() completes.
clean_form¶
Override in your form class for cross-field validation. Called only after validate() succeeds. Use self.add_error() to attach errors to specific fields. The base implementation returns True.
add_error¶
Attach a validation error to the named field. Intended for use inside clean_form(). Raises FormError if field_name does not exist.
get_data / set_data¶
get_data() returns a snapshot of all current field values. set_data() updates the named fields without affecting others.
layout¶
def layout(
self,
using: type[FormLayout] | Callable[..., Widget] | None = None,
*,
id: str | None = None,
) -> Widget
Return the widget that renders this form. Use with yield like any other Textual widget:
yield self.form.layout() # default layout
yield self.form.layout(MyTwoColumnLayout) # Widget subclass
yield self.form.layout(my_layout_fn) # callable returning a Widget
With no argument, returns a DefaultFormLayout widget (fields + Submit/Cancel buttons).
The default may be customised by setting layout_class on the form class.
With a FormLayout subclass, returns an instance of that class.
With a callable, calls using(form) and returns the result (must be a Widget).