Docs

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

Component Bindings

Using signals to build reactive user interfaces with component bindings.

Signals connect directly to Vaadin components through binding methods. When a signal value changes, bound components update automatically without manual listeners or state synchronization.

Note
Preview Feature

This is a preview version of Signals. You need to enable it with the feature flag com.vaadin.experimental.flowFullstackSignals. Preview versions may lack some planned features, and breaking changes may be introduced in any Vaadin version. We encourage you to try it out and provide feedback to help us improve it.

Overview

Vaadin components provide direct binding methods for common properties. These bindings create a reactive connection between your data and the UI. This section covers the general-purpose component-level binding APIs. For lower-level Element bindings, see Element Bindings.

Signal Basics

Signals hold reactive values. The most common type is ValueSignal. For complete coverage of signal types and operations, see Local Signals and Shared Signals.

Binding Text Content

Use bindText() to bind a component’s text content to a signal:

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

ValueSignal<String> name = new ValueSignal<>("World");

Span greeting = new Span();
greeting.bindText(name.map(n -> "Hello, " + n + "!"));
// Displays "Hello, World!"

name.value("Vaadin");
// Automatically updates to "Hello, Vaadin!"

Transforming Values

Use map() to transform signal values before display:

Source code
Java
import com.vaadin.signals.shared.SharedNumberSignal;

SharedNumberSignal counter = new SharedNumberSignal();

Button button = new Button();
button.bindText(counter.map(c -> String.format("Clicked %.0f times", c)));
// Displays "Clicked 0 times"

button.addClickListener(e -> counter.incrementBy(1));
// After click: "Clicked 1 times"

Combining Multiple Signals

Use a lambda to combine values from multiple signals:

Source code
Java
ValueSignal<String> firstName = new ValueSignal<>("John");
ValueSignal<String> lastName = new ValueSignal<>("Doe");

Span fullName = new Span();
fullName.bindText(() -> firstName.value() + " " + lastName.value());
// Displays "John Doe"

firstName.value("Jane");
// Automatically updates to "Jane Doe"

For expensive computations, wrap the lambda in Signal.computed() to cache the result:

Source code
Java
fullName.bindText(Signal.computed(() ->
    firstName.value() + " " + lastName.value()));

Binding Visibility

Use bindVisible() to show or hide components based on a boolean signal:

Source code
Java
ValueSignal<Boolean> showDetails = new ValueSignal<>(false);

Div detailsPanel = new Div();
detailsPanel.setText("Detailed information here...");
detailsPanel.bindVisible(showDetails);
// Panel is hidden initially

Button toggleButton = new Button("Show Details");
toggleButton.addClickListener(e -> showDetails.update(v -> !v));
// Clicking toggles panel visibility

Conditional Visibility

Derive visibility from other signal types using map():

Source code
Java
ValueSignal<String> searchText = new ValueSignal<>("");

Span noResults = new Span("No results found");
noResults.bindVisible(searchText.map(text -> text.isEmpty()));
// Shows when search text is empty

Div results = new Div();
results.bindVisible(searchText.map(text -> !text.isEmpty()));
// Shows when search text is not empty

Binding Enabled State

Use bindEnabled() to enable or disable components based on a boolean signal:

Source code
Java
ValueSignal<Boolean> termsAccepted = new ValueSignal<>(false);

Button submitButton = new Button("Submit");
submitButton.bindEnabled(termsAccepted);
// Button is disabled initially

Checkbox termsCheckbox = new Checkbox("I accept the terms");
termsCheckbox.bindValue(termsAccepted);
// Button enables when checkbox is checked

Form Validation Example

Enable a submit button only when the form is valid:

Source code
Java
ValueSignal<String> email = new ValueSignal<>("");
ValueSignal<String> password = new ValueSignal<>("");

Signal<Boolean> formValid = Signal.computed(() -> {
    String e = email.value();
    String p = password.value();
    return e.contains("@") && p.length() >= 8;
});

Button submitButton = new Button("Sign Up");
submitButton.bindEnabled(formValid);
// Button enables only when email contains @ and password has 8+ characters

Two-Way Form Field Binding

Use bindValue() on form fields to create two-way binding. Changes to the field update the signal, and changes to the signal update the field:

Source code
Java
import com.vaadin.signals.shared.SharedValueSignal;

SharedValueSignal<String> username = new SharedValueSignal<>(String.class);

TextField usernameField = new TextField("Username");
usernameField.bindValue(username);

// User types "john" -> username.value() returns "john"
// username.value("jane") -> field displays "jane"

Supported Field Types

Two-way binding works with all form fields implementing HasValue:

Source code
Java
// Text fields
TextField textField = new TextField();
textField.bindValue(stringSignal);

TextArea textArea = new TextArea();
textArea.bindValue(stringSignal);

// Checkbox
SharedValueSignal<Boolean> accepted = new SharedValueSignal<>(Boolean.class);
Checkbox checkbox = new Checkbox("Accept terms");
checkbox.bindValue(accepted);

// Number field
SharedValueSignal<Double> amount = new SharedValueSignal<>(Double.class);
NumberField numberField = new NumberField("Amount");
numberField.bindValue(amount);

// Select/ComboBox
SharedValueSignal<String> country = new SharedValueSignal<>(String.class);
ComboBox<String> countrySelect = new ComboBox<>("Country");
countrySelect.setItems("USA", "UK", "Germany", "France");
countrySelect.bindValue(country);

// Date picker
SharedValueSignal<LocalDate> birthDate = new SharedValueSignal<>(LocalDate.class);
DatePicker datePicker = new DatePicker("Birth date");
datePicker.bindValue(birthDate);

Read-Only Binding

Use bindReadOnly() to control the read-only state of a form field:

Source code
Java
ValueSignal<Boolean> locked = new ValueSignal<>(false);

TextField field = new TextField("Document title");
field.bindReadOnly(locked);
// Field is editable initially

Button lockButton = new Button("Lock", e -> locked.value(true));
// After clicking: field becomes read-only

Binding Theme Variants

Use getThemeList().bind() to toggle theme variants based on a signal:

Source code
Java
ValueSignal<Boolean> darkMode = new ValueSignal<>(false);

VerticalLayout layout = new VerticalLayout();
layout.getThemeList().bind("dark", darkMode);
// Dark theme applied when darkMode is true

Button toggleTheme = new Button("Toggle Dark Mode");
toggleTheme.addClickListener(e -> darkMode.update(v -> !v));

Multiple Theme Variants

Bind multiple independent theme variants:

Source code
Java
ValueSignal<Boolean> compact = new ValueSignal<>(false);
ValueSignal<Boolean> rowStripes = new ValueSignal<>(true);

Grid<Person> grid = new Grid<>();
grid.getThemeList().bind("compact", compact);
grid.getThemeList().bind("row-stripes", rowStripes);

Binding Inline Styles

Use getStyle().bind() to bind CSS properties to signals:

Source code
Java
ValueSignal<String> backgroundColor = new ValueSignal<>("white");

Div panel = new Div();
panel.getStyle().bind("background-color", backgroundColor);

// Change background color
backgroundColor.value("lightblue");

Dynamic Styling Example

Create dynamic visual feedback based on data:

Source code
Java
SharedNumberSignal progress = new SharedNumberSignal();

Div progressBar = new Div();
progressBar.getStyle().set("height", "20px");
progressBar.getStyle().set("transition", "width 0.3s");
progressBar.getStyle().bind("width", progress.map(p -> p + "%"));
progressBar.getStyle().bind("background-color", progress.map(p ->
    p < 50 ? "orange" : (p < 100 ? "blue" : "green")));

Rendering Lists of Components

Use ComponentEffect.bindChildren() to efficiently render a list of components from a list signal. This method handles additions, removals, and reordering without recreating all children:

Source code
Java
import com.vaadin.signals.shared.SharedListSignal;

SharedListSignal<String> items = new SharedListSignal<>(String.class);

VerticalLayout container = new VerticalLayout();

ComponentEffect.bindChildren(container, items, itemSignal -> {
    Span itemView = new Span();
    itemView.bindText(itemSignal);
    return itemView;
});

// Add items
items.insertLast("First item");
items.insertLast("Second item");
// Container now has two Span children

Complex List Items

Create rich list items with nested bindings:

Source code
Java
record Todo(String text, boolean done) {
    Todo withDone(boolean done) {
        return new Todo(this.text, done);
    }
}

SharedListSignal<Todo> todos = new SharedListSignal<>(Todo.class);

VerticalLayout todoList = new VerticalLayout();

ComponentEffect.bindChildren(todoList, todos, todoSignal -> {
    HorizontalLayout row = new HorizontalLayout();
    row.setAlignItems(FlexComponent.Alignment.CENTER);

    // Two-way binding to the 'done' property using signal mapping
    Checkbox checkbox = new Checkbox();
    checkbox.bindValue(todoSignal.map(Todo::done, Todo::withDone));

    Span text = new Span();
    text.bindText(todoSignal.map(Todo::text));

    // Strike through completed items
    text.getStyle().bind("text-decoration",
        todoSignal.map(t -> t.done() ? "line-through" : "none"));

    Button editButton = new Button("Edit", e -> {
        // Update the todo's text - this only updates the state,
        // the component is not recreated
        todoSignal.update(t -> new Todo(t.text() + " (edited)", t.done()));
    });

    Button deleteButton = new Button("Delete", e -> {
        todos.remove(todoSignal);
    });

    row.add(checkbox, text, editButton, deleteButton);
    return row;
});

The map(getter, merger) method creates a two-way mapping that enables direct binding between the checkbox and the done property. See Two-Way Signal Mapping for more details.

When you update a todo item using todoSignal.update(), only the bound properties (text content, styling) are updated. The component itself is not recreated, maintaining its position and identity in the DOM.

Efficiency

The factory function runs once per list item. When you modify the list:

  • Adding items: Only new items create new components

  • Removing items: Only removed items are detached

  • Reordering: Components are moved, not recreated

  • Updating values: Item bindings update, but components aren’t recreated

Complete Example

Here’s a complete example combining multiple binding types:

Source code
Java
public class TaskManager extends VerticalLayout {
    // State
    private final SharedListSignal<Task> tasks = new SharedListSignal<>(Task.class);
    private final ValueSignal<String> newTaskText = new ValueSignal<>("");
    private final ValueSignal<Boolean> showCompleted = new ValueSignal<>(true);

    // Computed signals
    private final Signal<Long> totalCount = Signal.computed(() ->
        (long) tasks.value().size());
    private final Signal<Long> completedCount = Signal.computed(() ->
        tasks.value().stream()
            .filter(t -> t.value().done())
            .count());

    public TaskManager() {
        // Header with statistics
        Span stats = new Span();
        stats.bindText(Signal.computed(() ->
            String.format("Tasks: %d total, %d completed",
                totalCount.value(), completedCount.value())));
        add(stats);

        // Add task form
        HorizontalLayout addForm = new HorizontalLayout();
        TextField taskInput = new TextField();
        taskInput.setPlaceholder("Enter new task");
        taskInput.bindValue(newTaskText);

        Button addButton = new Button("Add Task");
        addButton.bindEnabled(newTaskText.map(t -> !t.isBlank()));
        addButton.addClickListener(e -> {
            tasks.insertLast(new Task(newTaskText.value(), false));
            newTaskText.value("");
        });

        addForm.add(taskInput, addButton);
        add(addForm);

        // Show/hide completed toggle
        Checkbox showCompletedCheckbox = new Checkbox("Show completed tasks");
        showCompletedCheckbox.bindValue(showCompleted);
        add(showCompletedCheckbox);

        // Task list
        VerticalLayout taskList = new VerticalLayout();
        taskList.setPadding(false);

        ComponentEffect.bindChildren(taskList, tasks, taskSignal -> {
            HorizontalLayout row = new HorizontalLayout();
            row.setAlignItems(FlexComponent.Alignment.CENTER);
            row.setWidthFull();

            // Hide completed tasks when toggle is off
            row.bindVisible(Signal.computed(() ->
                showCompleted.value() || !taskSignal.value().done()));

            // Two-way binding to the 'done' property
            Checkbox doneCheckbox = new Checkbox();
            doneCheckbox.bindValue(taskSignal.map(Task::done, Task::withDone));

            Span taskText = new Span();
            taskText.bindText(taskSignal.map(Task::text));
            taskText.getStyle().bind("text-decoration",
                taskSignal.map(t -> t.done() ? "line-through" : "none"));

            Button deleteButton = new Button(VaadinIcon.TRASH.create());
            deleteButton.addClickListener(e -> tasks.remove(taskSignal));

            row.add(doneCheckbox, taskText, deleteButton);
            return row;
        });

        add(taskList);
    }

    record Task(String text, boolean done) {
        Task withDone(boolean done) {
            return new Task(this.text, done);
        }
    }
}

This example demonstrates:

  • Two-way binding with form fields (bindValue())

  • Two-way property mapping with map(getter, merger) (see Two-Way Signal Mapping)

  • Conditional button enabling (bindEnabled())

  • Computed signals for derived values

  • List rendering with ComponentEffect.bindChildren()

  • Dynamic visibility (bindVisible())

  • Dynamic styling (getStyle().bind())