Skip to content
ssbaxys edited this page Oct 26, 2026 · 1 revision

Input validation, forms and setup wizards.

Input validation and forms

Validators (Validator) attach to any field (TextField, NumberField, ColorField):

new TextField(login).validate(Validator.required(), Validator.minLength(3),
        Validator.pattern("[A-Za-z0-9_]+", "Only Latin letters, digits and _"));
new NumberField(age).validate(Validator.range(13, 120, "Age must be 13 to 120"));
new TextField(repeat).validate(Validator.sameAs(password::peek, "Passwords don't match"));
Validator.of(s -> s.chars().anyMatch(Character::isDigit), "Needs a digit");   // your own check
// more: Validator.email(), integer(), maxLength(16), v.and(other), v.message("Custom message"); field.required()

All ready-made validators except required accept empty text. The error shows when the field loses focus after an edit, and then updates on every edit. On a field: error() is the shown error (a signal), valid() whether the text passes right now (a signal), validate() checks and shows, check() checks silently, setError("Name taken") sets your own error (a server reply), resetValidation(). An invalid field gets the FieldBase.INVALID state and a red border.

Form row (FormField): a label, the field, a hint (hint) and an error that slides out below the field, making room. Form (Form) collects the fields:

Form form = new Form()
        .onSubmit(() -> register(nick.peek()))
        .onInvalid(() -> toast("Fix the errors"));
panel.add(
        form.field("Nickname", new TextField(nick).required()).hint("3–16 characters"),
        form.field("Email", new TextField(mail).validate(Validator.required(), Validator.email())),
        form.submitButton(new Button("Create"), false));    // true disables the button while the form is invalid
form.check(() -> agree.peek());      // your own whole-form check
form.valid();                        // "all valid" signal, changes on every edit
form.validate(); form.submit(); form.reset(); form.errors();
new Form().collect(markupRoot);      // all validated fields inside an element

submit() commits pending input, shows errors on all fields at once and focuses the first invalid one. Styles: form-field, form-label, form-hint, form-error. In markup: <form>, <field> and the required, minlength, pattern attributes (see HTML and CSS).

Setup wizard (Wizard) and step indicator (Stepper)

Stepper shows numbered circles with a progress line and labels: completed steps get a check mark, the current one pulses, the line stretches to the step with a spring. new Stepper(step, "Account", "Profile", "Done").clickable(i -> i < step.peek()).

Wizard is a Stepper, the step page (sliding via Pages) and Back / Next / Finish buttons. Next runs the step's check first; if it fails, the page shakes and stays.

new Wizard()
        .step("Server", serverPage, serverForm)               // the check is the step's Form (errors under fields)
        .step("Mode", this::modePage, () -> mode.peek() != null)
        .step("Done", this::summary)
        .labels("Back", "Next", "Create server")
        .onFinish(this::create);
wizard.step();   // Signal<Integer> with the step number; next(), back(), isFinished()

Styles: stepper, wizard, wizard-nav.


← Popups · English home · Markdown and Books →

Clone this wiki locally