Layouts¶
Layout classes are Textual Widget subclasses (specifically VerticalScroll subclasses) that render a form's fields and handle submit/cancel interactions.
FormLayout¶
FormLayout
¶
Bases: VerticalScroll
Base class for form renderers.
Subclass and override compose() to create custom layouts. The base class handles button events and keyboard shortcuts.
FormLayout is the base class for all form layouts. It provides:
self.form— theBaseForminstance passed at constructionEnterkeybinding →action_submit()→ postsForm.Submitted(after successfulclean())Escapekeybinding →action_cancel()→ postsForm.Cancelledon_button_pressedhandler that routes button IDs"submit"and"cancel"
Subclass FormLayout directly only if you also need to handle widget events yourself. Otherwise subclass ControllerAwareLayout.
Bindings¶
| Key | Action | Description |
|---|---|---|
enter |
submit |
Validate and submit the form |
escape |
cancel |
Cancel without submitting |
Constructor¶
form- The
BaseForminstance to render. All field data and validation logic lives on this object. id- Optional Textual widget ID.
ControllerAwareLayout¶
ControllerAwareLayout
¶
Bases: FormLayout
FormLayout mixin that routes widget events to FieldControllers.
When a form is composed with raw inner widgets (via BoundField.__call__
rather than BoundField.simple_layout), those widgets are not inside a
FieldWidget that would handle events for them. This mixin catches the
relevant Textual events and routes them to the appropriate
FieldController based on the ._field_controller attribute stamped
on each inner widget by BoundField.__call__.
Events that originate from inside a FieldWidget are ignored here
(the FieldWidget handles them directly).
ControllerAwareLayout extends FormLayout by routing Textual widget events to FieldController objects. This is the class you should subclass when building custom layouts that use bf.__call__() to place raw widgets.
How event routing works¶
When you call bf() to get a raw widget, the BoundField stamps ._field_controller on the returned widget. ControllerAwareLayout listens for Input.Changed, Checkbox.Changed, Select.Changed, TextArea.Changed, and on_descendant_blur events. For each event, it looks up the controller and calls the appropriate method (handle_widget_input or validate_for).
Events originating from within a FieldWidget (the composite widget produced by bf.simple_layout()) are ignored here — the FieldWidget handles them internally.
When to use ControllerAwareLayout¶
Use ControllerAwareLayout (or its subclass DefaultFormLayout) as your layout base class whenever you use bf() to render raw widgets. If you use only bf.simple_layout(), the FieldWidget handles its own events and you could technically use bare FormLayout — but ControllerAwareLayout handles both cases gracefully, so it is always safe to use.
DefaultFormLayout¶
DefaultFormLayout
¶
Bases: ControllerAwareLayout
Renders all fields in declaration order with default styling.
Adds a title bar (if the form has a title) and Submit/Cancel buttons.
Each field is rendered via BoundField.simple_layout().
DefaultFormLayout is the layout returned by form.layout() when no custom layout_class is set. It renders:
- A bold title label (if
form.titleis non-empty) - Each unrendered field via
bf.simple_layout() - A row of Submit (primary) and Cancel buttons
Default CSS¶
DefaultFormLayout {
height: auto;
max-height: 100%;
padding: 1 2;
}
DefaultFormLayout .form-title {
text-style: bold;
margin-bottom: 1;
}
DefaultFormLayout #buttons {
height: auto;
margin-top: 1;
}
Override these styles in your app's CSS to resize or reposition the layout:
Unrendered guard¶
DefaultFormLayout.compose() skips any field that has already been rendered (bf.controller.is_consumed is True). This lets you render some fields manually in a custom section before calling form.layout(), though mixing the two approaches in the same form is unusual.