> Markdown version of [Form Binding with Dynamic Validation](https://vaadin.com/docs/latest/flow/ui-state/usage-examples/binder-integration). Section index: [llms.txt](https://vaadin.com/docs/latest/flow/llms.txt)

# Form Binding with Dynamic Validation

This guide shows how to combine Vaadin Binder with signals for forms that have validation rules depending on other field values. You’ll learn how to use signals for cross-field validation and bind UI elements to validation status, all while leveraging Binder’s built-in validation features.

## <a id="the-use-case"></a>The Use Case

Forms often need validation rules that depend on other field values. For example, a registration form where age requirements change based on account type: personal accounts need users to be 14 or older, while business accounts require 18 or older. Additionally, you want to show real-time validation status and enable the submit button only when all fields are valid.

Signals simplify this by making validation rules and UI state reactive. When a user changes the account type, validation rules automatically update and the UI reflects the new state immediately—no manual coordination needed.

## <a id="the-solution"></a>The Solution

`BinderIntegrationExample.java`

```java
public BinderIntegrationExample() {
    // Create binder for form validation
    Binder<UserRegistration> binder = new Binder<>(UserRegistration.class);

    // Create form fields
    TextField usernameField = new TextField("Username");
    EmailField emailField = new EmailField("Email");
    PasswordField passwordField = new PasswordField("Password");
    PasswordField confirmPasswordField = new PasswordField(
            "Confirm Password");
    ComboBox<AccountType> accountTypeSelect = new ComboBox<>("Account Type",
            AccountType.values());
    IntegerField ageField = new IntegerField("Age");

    // Create signals for dynamic validation
    ValueSignal<AccountType> accountTypeSignal = new ValueSignal<>(
            AccountType.PERSONAL);
    ValueSignal<Integer> ageSignal = new ValueSignal<>(0);

    // Bind signals to form fields for two-way updates
    accountTypeSelect.bindValue(accountTypeSignal, accountTypeSignal::set);
    ageField.bindValue(ageSignal, ageSignal::set);

    // Computed signal for age validation that depends on account type
    Signal<Boolean> ageValidSignal = Signal.computed(() -> {
        Integer age = ageSignal.get();
        AccountType accountType = accountTypeSignal.get();
        if (age == null) {
            return false;
        }
        // Business accounts require age >= 18, Personal accounts >= 14
        return accountType == AccountType.BUSINESS ? age >= 18 : age >= 14;
    });

    // Binder field validations
    // username and email validation is skipped for brevity
    binder.forField(usernameField).bind("username");
    binder.forField(emailField).bind("email");

    // Store password binding for cross-field validation
    Binder.Binding<UserRegistration, String> pwBinding = binder
            .forField(passwordField)
            .withValidator(value -> value != null && value.length() >= 8,
                    "Password must be at least 8 characters")
            .bind("password");

    // Cross-field validation using Binder.Binding.valueSignal()
    // Runs each time the password field changes
    binder.forField(confirmPasswordField)
            .withValidator(
                    value -> value != null
                            && value.equals(pwBinding.valueSignal().get()),
                    "Passwords do not match")
            .bind("confirmPassword");

    binder.forField(accountTypeSelect).bind("accountType");

    // Cross-field validation using signals for dynamic error messages
    // Runs each time the age or account field changes
    binder.forField(ageField)
            .withValidator(value -> ageValidSignal.get(), value -> {
                AccountType accountType = accountTypeSignal.get();
                return accountType == AccountType.BUSINESS
                        ? "Business accounts require age 18 or older"
                        : "Personal accounts require age 14 or older";
            }).bind("age");

    // Initialize the binder with an empty bean
    binder.readBean(
            new UserRegistration("", "", "", "", AccountType.PERSONAL, 0));

    // Submit button enabled only when form is valid
    Button submitButton = new Button("Register", e -> {
        UserRegistration userRegistration = new UserRegistration();
        binder.writeBeanIfValid(userRegistration);
        // Handle registration...
    });
    submitButton.bindEnabled(binder.validationStatusSignal()
            .map(BinderValidationStatus::isOk));

    // Form status display with reactive styling
    Div statusDiv = new Div();
    Span statusLabel = new Span();
    statusLabel.bindText(binder.validationStatusSignal()
            .map(status -> status.isOk() ? "Form is valid - Ready to submit"
                    : "Please complete all required fields correctly"));
    statusLabel.getStyle().bind("color", binder.validationStatusSignal()
            .map(status -> status.isOk() ? "green" : "orange"));
    statusDiv.add(statusLabel);

    add(usernameField, emailField, passwordField, confirmPasswordField,
            accountTypeSelect, ageField, statusDiv, submitButton);
}
```

## <a id="how-it-works"></a>How It Works

### <a id="signals-for-dynamic-values"></a>Signals for Dynamic Values

Create `ValueSignal` instances for fields that affect validation logic:

```java
ValueSignal<AccountType> accountTypeSignal = new ValueSignal<>(AccountType.PERSONAL);
ValueSignal<Integer> ageSignal = new ValueSignal<>(0);
```

These signals track the current values of fields that other validation rules depend on. When these values change, dependent validations update automatically.

### <a id="two-way-binding-with-form-fields"></a>Two-Way Binding with Form Fields

Use `bindValue()` to connect signals to form fields:

```java
accountTypeSelect.bindValue(accountTypeSignal, accountTypeSignal::set);
ageField.bindValue(ageSignal, ageSignal::set);
```

This creates a two-way connection: when the user changes the field, the signal updates, and when code updates the signal, the field updates. See [Two-Way Signal Mapping](https://vaadin.com/docs/latest/flow/ui-state/local-signals.md#two-way-mapping) for more details about bidirectional bindings.

### <a id="computed-signals-for-dynamic-validation"></a>Computed Signals for Dynamic Validation

Create a computed signal that derives the validation result from multiple signals:

```java
Signal<Boolean> ageValidSignal = Signal.computed(() -> {
    Integer age = ageSignal.get();
    AccountType accountType = accountTypeSignal.get();
    if (age == null) {
        return false;
    }
    return accountType == AccountType.BUSINESS ? age >= 18 : age >= 14;
});
```

This signal automatically recalculates whenever `ageSignal` or `accountTypeSignal` changes. The validation logic is centralized and reactive.

### <a id="using-signals-in-binder-validators"></a>Using Signals in Binder Validators

Reference the computed signal in your Binder validator:

```java
binder.forField(ageField)
    .withValidator(value -> ageValidSignal.get(), value -> {
        AccountType accountType = accountTypeSignal.get();
        return accountType == AccountType.BUSINESS
            ? "Business accounts require age 18 or older"
            : "Personal accounts require age 14 or older";
    })
    .bind("age");
```

The validator checks `ageValidSignal.get()` and provides a dynamic error message based on the current account type. When the user changes the account type, Binder automatically re-runs validation because the signal value changed.

### <a id="cross-field-validation-with-binding-values"></a>Cross-Field Validation with Binding Values

For simpler cross-field validation that doesn’t need separate signal variables, use `Binder.Binding.valueSignal()` — the binding validator re-runs whenever a binding signal value changes:

```java
Binder.Binding<UserRegistration, String> pwBinding = binder
    .forField(passwordField)
    .withValidator(value -> value != null && value.length() >= 8,
        "Password must be at least 8 characters")
    .bind("password");

binder.forField(confirmPasswordField)
    .withValidator(value -> value != null && value.equals(pwBinding.valueSignal().get()),
        "Passwords do not match")
    .bind("confirmPassword");
```

You can mix `binding.valueSignal()` and `signal.get()` in the same validator — Binder re-validates whenever any referenced signal value changes, whether it comes from a binding or a standalone signal. Use standalone signals when you need to share validation state across multiple components or derive complex logic from multiple sources.

### <a id="reactive-submit-button"></a>Reactive Submit Button

Enable the submit button only when the form is valid using `bindEnabled()`:

```java
submitButton.bindEnabled(
    binder.validationStatusSignal().map(BinderValidationStatus::isOk));
```

The `validationStatusSignal()` method returns a signal that updates whenever validation state changes. The `map()` method transforms it to a boolean that controls the button’s enabled state.

### <a id="reactive-status-display"></a>Reactive Status Display

Show form status with reactive text and styling:

```java
Span statusLabel = new Span();
statusLabel.bindText(binder.validationStatusSignal()
    .map(status -> status.isOk() ? "Form is valid - Ready to submit"
        : "Please complete all required fields correctly"));
statusLabel.getStyle().bind("color", binder.validationStatusSignal()
    .map(status -> status.isOk() ? "green" : "orange"));
```

Multiple bindings to the same signal are efficient - the signal value is cached and shared across all dependent components.

## <a id="key-patterns"></a>Key Patterns

- **Use signals for dynamic validation**

  When validation rules depend on other field values, use a binding value or a signal that holds a dependency state. Binder automatically re-validates when binding or signal values change.

- **Two-way binding with `bindValue()`**

  Connect form fields to signals so changes flow in both directions automatically.

- **Computed signals for derived logic**

  Create computed signals that derive validation state from multiple source signals. This centralizes logic and makes dependencies explicit.

- **Reactive UI elements**

  Bind buttons, labels, and styles to Binder’s validation status for real-time feedback.

## <a id="when-to-use-this-pattern"></a>When to Use This Pattern

This pattern is ideal when:

- Validation rules depend on other form fields

- Error messages need to be dynamic based on field values

- You want real-time validation feedback as users type

- You need to enable/disable submit buttons based on form validity

For simpler forms with static validation rules, standard Binder validators without signals may be sufficient.

## <a id="related-topics"></a>Related Topics

- [CRUD Form Editor](https://vaadin.com/docs/latest/flow/ui-state/usage-examples/crud-editor.md) - Using Binder with a signal of a bean to create a CRUD editor for selected item

- [Local Signals](https://vaadin.com/docs/latest/flow/ui-state/local-signals.md) - Understanding ValueSignal and two-way binding

- [Effects and Computed Signals](https://vaadin.com/docs/latest/flow/ui-state/effects-computed.md) - Creating derived values

- [Component Bindings](https://vaadin.com/docs/latest/flow/ui-state/building-ui.md) - Binding signals to component properties

- [Binder Validation](https://vaadin.com/docs/latest/flow/binding-data/components-binder-validation.md) - Reference documentation for Binder validation patterns
