Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Forms

Supported form controls bind to Bevy resource models through .bind(...). User edits call the generated or custom setter; successful values are read back so normalization reaches the control. Model-side changes synchronize to controls on the next UI update.

Binding Controls

Configure the concrete builder first, then call .bind(...). Binding returns a UiElement that can be composed in a Form, Container, or another section.

#![allow(unused)]
fn main() {
use beverly::prelude::*;
use beverly::components::input::TextInputConfig;

#[model]
struct User {
    email: String,
    active: bool,
    priority: u32,
}

let user = User {
    email: String::new(),
    active: false,
    priority: 1,
};
let fields = [
    TextInputConfig::new("Email").label("Email address").bind(User::email),
    CheckboxConfig::new().label("Active").bind(User::active),
    Select::new(vec![1u32, 2, 3]).bind(User::priority),
];
}

Insert the model as a Bevy resource. BeverlyPlugin installs BindingPlugin; a custom app must install it explicitly. Configure concrete controls before binding because .bind(...) consumes the builder and returns UiElement.

Control builderBound value
TextInputConfigString, numeric scalar, or bool represented as text
TextareaConfigString
CheckboxConfig, ToggleConfigbool
RadioGroupBuilderString option value
Select<T>T: BindingValueType + Display
SliderNumeric scalar within the slider range
DropdownConfigString option value, not its display label
FileInputVec<SelectedFile>
Search / SearchBoxString

BindingValueType has built-in conversions for String, bool, numeric scalar values, and Vec<SelectedFile>. Custom value types can implement the trait. Typed Select<T> additionally requires Display for accessible option labels. The selected model value must match one of the Select’s options; an unmatched value becomes a BindingErrorState instead of silently selecting another option. Register SelectPlugin<T> for every concrete option type used.

For custom field accessors, create FieldBinding::new(getter, setter). The getter returns the model value; the setter accepts &mut Model and returns Result<(), BindingErrors>. A setter alone cannot support two-way binding because the binding also needs a getter.

Pending and Success

Use .submitting(|| ...) and .success(|| ...) to add state-specific content below a value control, Form, or submit control. Callbacks take no arguments and return one UiElement or an iterable. They do not inspect models, messages, or requests automatically.

#![allow(unused)]
fn main() {
let form = UiElement::form(Form::new())
    .submitting(|| [spinner(), text("Saving changes…")])
    .success(|| text("Saved"))
    .spawn_in_context(&mut context);

context.world.get_mut::<FormStatusState>(form).unwrap().submitting();
// After the app's operation completes:
context.world.get_mut::<FormStatusState>(form).unwrap().success();
context.world.get_mut::<FormStatusState>(form).unwrap().idle();
}

The app owns the lifecycle. Callback content replaces on status changes and is removed on Idle; it has polite live-region semantics. Controls are not automatically disabled while submitting. Configure that policy separately. See Form for sections and complete composition.

Error Callbacks

Value controls, typed Select<T>, Form, and submit controls expose .error(|errors| ...). The callback receives the current &[String] and returns composable content. For bound fields, setter/conversion failures update BindingErrorState automatically. Form-level and unbound-control errors are app-owned and must be set on that entity.

#![allow(unused)]
fn main() {
let form = Form::new()
    .error(|errors| text(errors.join("\n")))
    .spawn_in_context(&mut context);

context.world.get_mut::<BindingErrorState>(form).unwrap()
    .error(BindingErrors::new("Unable to save. Please retry."));
}

Changed nonempty errors replace the presentation; recovery removes it. Field errors are not automatically aggregated into Form-level errors. Invalid state, error announcements, and relationships are projected into accessibility semantics; verify delivery with the platforms and screen readers you support.

Submission

A generated Form submit control gathers named descendant values and emits FormSubmitted. The app decides how to perform a request and handle its result. It does not send network traffic automatically. See Submit for GET, POST, PUT, PATCH, body encodings, and the optional blocking transport feature.