Skip to content

Forms

gpui-starter includes a working registration form that demonstrates:

  • gpui-form: derive macros that generate form UI and state management
  • koruma: validation rules with composable builders
  • es-fluent: localized error messages

The form is in src/features/pages/form_page.rs.

The RegistrationForm struct derives multiple macros:

#[derive(Clone, Debug, Default, EsFluentVariants, GpuiForm, Koruma, KorumaAllFluent)]
#[fluent_variants(keys = ["description", "label"])]
#[gpui_form(koruma(fluent))]
pub struct RegistrationForm {
#[gpui_form(component(gpui_form_collection::input::Input::<_>))]
#[koruma(NonEmptyValidation::<_>)]
pub name: String,
#[gpui_form(component(gpui_form_collection::input::Input::<_>))]
#[koruma(EmailValidation::<_>)]
pub email: String,
#[gpui_form(component(gpui_form_collection::input::Input::<_>))]
#[koruma(NonEmptyValidation::<_>)]
pub password: String,
#[gpui_form(component(gpui_form_collection::input::Input::<_>))]
#[koruma(PhoneNumberValidation::<_>)]
pub phone: String,
#[gpui_form(component(gpui_form_collection::input::Input::<_>))]
#[koruma(UrlValidation::<_>)]
pub website: String,
}

The following validators from koruma-collection are used:

Validator Purpose Example
NonEmptyValidation Requires non-empty input Name, password
EmailValidation Validates email format user@example.com
PhoneNumberValidation Validates phone format (555) 123-4567
UrlValidation Validates URL format https://example.com

In the #[koruma(...)] attribute, validators are written as bare paths (NonEmptyValidation::<_>); chain option setters directly on the path when a validator needs configuration.

The #[fluent_variants(keys = ["description", "label"])] attribute generates typed label and description variants. Combined with KorumaAllFluent, validation errors are automatically localized.

Labels are defined in the .ftl files:

registration_form_label_variants-name = Full Name
registration_form_label_variants-email = Email
registration_form_description_variants-email = We'll never share your email with anyone else.

Use gpui-component’s v_form() and field() builders:

v_form()
.label_width(px(160.))
.child(
field()
.label(crate::i18n::localize_message(
&RegistrationFormLabelVariants::Name,
))
.required(true)
.description_fn({
// closure that renders description + errors
})
.child(Input::new(&self.fields.name)),
)

Validate on submit and show errors or success:

Button::new("submit")
.primary()
.label("Create Account")
.on_click(cx.listener(|this, _, window, cx| {
this.touched = true;
let valid = this.current_data.validate().is_ok();
if valid && this.agree_terms {
this.submitted = true;
window.push_notification("Form submitted successfully!", cx);
}
}))

Validation runs via self.current_data.validate() which returns Result<(), ValidationErrors>. Errors are then extracted per field and displayed inline.