Docs

Documentation versions (currently viewingVaadin 25.1 (pre-release))

Manage UI State with Signals

Using signals in Flow to manage UI state reactively in Vaadin applications.

Signals enable reactive state management for Vaadin Flow applications. Instead of manually updating UI components when data changes, you declare the relationship between your data and UI once, and signals keep everything synchronized automatically.

Component Bindings
Using signals to build reactive user interfaces with component bindings.
Local Signals
Using local signals for UI-only state management.
Effects and Computed Signals
Using effects and computed signals for reactive UI updates.
Shared Signals
Using shared signals for thread-safe, multi-user state management.
Element Bindings
Binding signals to Element properties, attributes, and styles for advanced use cases.
Transactions
Using transactions for atomic signal operations.
Usage Examples
Practical guides for solving common state management challenges with Signals

Why Signals?

Consider a simple counter-component consisting of a counter value and a button that increments the counter and updates the text of the button each time it is clicked. Without signals, you need to manually update the button text value whenever the counter changes:

Source code
Java
public class TraditionalCounter extends VerticalLayout {
    private int count = 0;

    public TraditionalCounter() {
        Button button = new Button("Clicked 0 times");
        button.addClickListener(click -> {
            count++;
            button.setText("Clicked " + count + " times"); // Manual UI update
        });
        add(button);
    }
}

With signals, the button text updates automatically. In this version, each time the button is clicked, the Integer value in the count ValueSignal is updated by adding 1 to it, and because the signal is bound to the text of the button, it tells the button to update itself with the new value:

Source code
Java
import com.vaadin.flow.signals.local.ValueSignal;

public class SignalCounter extends VerticalLayout {
    private final ValueSignal<Integer> count = new ValueSignal<>(0);

    public SignalCounter() {
        Button button = new Button();
        button.addClickListener(click -> count.update(c -> c + 1));
        add(button);

        // UI updates automatically when count changes
        button.bindText(count.map(c -> "Clicked " + c + " times"));
    }
}

The benefit becomes clear with more complex UIs. When multiple components depend on the same data, signals ensure they all stay in sync without manual coordination:

Source code
Java
public class UserProfile extends VerticalLayout {
    private final ValueSignal<String> firstName = new ValueSignal<>("");
    private final ValueSignal<String> lastName = new ValueSignal<>("");

    public UserProfile() {
        // Form fields
        TextField firstNameField = new TextField("First name");
        firstNameField.addValueChangeListener(e -> firstName.set(e.getValue()));

        TextField lastNameField = new TextField("Last name");
        lastNameField.addValueChangeListener(e -> lastName.set(e.getValue()));

        // Multiple UI elements that update automatically
        Span greeting = new Span();
        Span fullName = new Span();
        Button saveButton = new Button("Save");

        greeting.bindText(firstName.map(name -> "Hello, " + name + "!"));

        fullName.bindText(Signal.computed(() ->
            firstName.get() + " " + lastName.get()));

        saveButton.bindEnabled(Signal.computed(() ->
            !firstName.get().isEmpty() && !lastName.get().isEmpty()));

        add(firstNameField, lastNameField, greeting, fullName, saveButton);
    }
}

All three components (greeting, full name, save button) update automatically whenever the user types. No manual synchronization needed.

Key Features

Reactive

Changes to signal values automatically propagate to dependent parts of the UI.

Automatic Dependency Tracking

Effects automatically detect which signals they depend on.

Immutable Values

Signals work best with immutable values, such as strings or Java records, to ensure data consistency.

Transactions

Multiple operations can be grouped into a transaction that either succeeds or fails as a whole.

Thread-Safe

Signals can be updated from any thread without manual UI synchronization.

Core Concepts

Signals

A signal holds a value. When the value changes, all dependent parts of the UI update automatically without manual change listeners.

Source code
Java
ValueSignal<String> name = new ValueSignal<>("initial value");
name.set("new value"); // Set the value
String current = name.get(); // Get the value

Effects

Effects are the callbacks that run reactively to a state change and that update the UI accordingly. When you bind a signal to a component, an effect tracks which signals are read and re-runs when they change. In the example below, the effect is a lambda function passed into Signal.computed that updates the text of the span whenever firstName or lastName changes:

Source code
Java
// This binding creates an effect that updates text when signals change
span.bindText(Signal.computed(() ->
    firstName.get() + " " + lastName.get()));

For custom logic, you can create effects directly. In this example, the effect is a lambda callback passed into Signal.effect that prints a message to the console whenever firstName or lastName changes:

Source code
Java
Signal.effect(component, () -> {
    // This runs whenever firstName or lastName changes
    System.out.println("Name changed to: " + firstName.get() + " " + lastName.get());
});

Computed Signals

Computed signals derive their value from other signals and update automatically when dependencies change.

Source code
Java
Signal<String> fullName = Signal.computed(() ->
    firstName.get() + " " + lastName.get());

Transactions

Transactions group multiple signal operations into a single atomic unit. All operations succeed or fail together.

Source code
Java
Signal.runInTransaction(() -> {
    firstName.set("John");
    lastName.set("Doe");
    // Both update atomically - observers never see partial state
});

Local vs Shared Signals

Vaadin provides two categories of signals:

Local signals are for UI state within a single browser session - things like whether a panel is expanded, form validation state, or filter settings.

Shared signals are for state that needs synchronization across multiple users or requires transactional guarantees.

Local Signals Shared Signals

Scope

Single UI instance

Multiple users/sessions

Cluster Support

Single server only

Works across cluster

Transactions

No

Yes

Primary Classes

ValueSignal, ListSignal

SharedValueSignal, SharedNumberSignal, SharedListSignal, etc.

Choosing Between Local and Shared Signals

Use local signals when:

  • The state is only relevant to a single user’s UI session

  • You need simple, fast state management without synchronization overhead

  • You’re managing UI state like form visibility, panel expansion, or local filters

  • You need dynamic lists for a single user (use ListSignal for add/remove operations)

Use shared signals when:

  • Multiple users need to see the same data in real-time

  • You need transactional guarantees for state changes

  • You’re building collaborative features like live dashboards or multi-user editing

Note
When using shared signals for multi-user scenarios, you need to enable push so changes are pushed to all users immediately.