> Markdown version of [Component Bindings](https://vaadin.com/docs/latest/flow/ui-state/building-ui). Section index: [llms.txt](https://vaadin.com/docs/latest/flow/llms.txt)

# Component Bindings (since V25.1)

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

## <a id="overview"></a>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](https://vaadin.com/docs/latest/flow/ui-state/element-bindings.md).

## <a id="signal-basics"></a>Signal Basics

Signals hold reactive values. The most common type is `ValueSignal`. For complete coverage of signal types and operations, see [Local Signals](https://vaadin.com/docs/latest/flow/ui-state/local-signals.md) and [Shared Signals](https://vaadin.com/docs/latest/flow/ui-state/shared-signals.md).

## <a id="binding-text-content"></a>Binding Text Content

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

```java
import com.vaadin.flow.signals.local.ValueSignal;

ValueSignal<String> greetingSignal = new ValueSignal<>("Hello");

Span greetingSpan = new Span();
greetingSpan.bindText(greetingSignal);
// Displays "Hello!"

name.set("Hi");
// Automatically updates to "Hi"
```

### <a id="transforming-values"></a>Transforming Values

Use `map()` to transform signal values before display:

```java
import com.vaadin.flow.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"
```

### <a id="combining-multiple-signals"></a>Combining Multiple Signals

Use a lambda to combine values from multiple signals:

```java
ValueSignal<String> firstName = new ValueSignal<>("John");
ValueSignal<String> lastName = new ValueSignal<>("Doe");

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

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

When combining signals involves an expensive computation that’s read multiple times, wrap it in `Signal.cached()` to avoid recomputing on every read:

```java
Span report = new Span();
report.bindText(Signal.cached(() ->
    generateReport(firstName.get(), lastName.get())));
```

A plain lambda or `Signal.computed()` signal recalculates the result every time someone reads the signal value, while a cached signal stores the result and reuses it until any dependency signal value changes. For simple operations like string concatenation, a lambda is enough. For complex or expensive computations that are read multiple times, use `Signal.cached()` to avoid unnecessary recalculations. See [Caching with Signal.cached()](https://vaadin.com/docs/latest/flow/ui-state/effects-computed.md#caching-with-signal-cached) for details.

> **Tip:** HTML components like `Paragraph`, `Span`, `H1`–`H6`, and others have signal-based constructors as a shorthand for creating a component and binding its text in one step:
>
> ```java
> // Shorthand
> Paragraph p = new Paragraph(signal);
>
> // Equivalent to
> Paragraph p = new Paragraph();
> p.bindText(signal);
> ```

## <a id="binding-visibility"></a>Binding Visibility

Use `bindVisible()` to show or hide components based on a boolean signal. This is how you express rules such as "show the **Done** button only while a row is being edited" or "hide the **Save** button until the form has unsaved changes": declare the rule once, and the component follows the state instead of being updated from every listener that changes it.

```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
```

Prefer a binding over calling `setVisible()` from listeners whenever visibility follows application state. While a signal is bound, calling `setVisible()` on the component throws a `BindingActiveException`; pass `null` to `bindVisible()` to remove the binding. For `setVisible()` semantics and how visibility affects the DOM, see [Component Visibility](https://vaadin.com/docs/latest/flow/component-internals/visibility.md).

### <a id="conditional-visibility"></a>Conditional Visibility

Derive visibility from other signal types using `map()`:

```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
```

Use a lambda when the condition reads more than one signal:

```java
ValueSignal<Boolean> formDirty = new ValueSignal<>(false);
ValueSignal<Boolean> submitting = new ValueSignal<>(false);

Button saveButton = new Button("Save");
saveButton.bindVisible(() -> formDirty.get() && !submitting.get());
// Shows only while there are unsaved changes and no submission is in progress
```

## <a id="binding-enabled-state"></a>Binding Enabled State

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

```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, termsAccepted::set);
// Button enables when checkbox is checked
```

### <a id="form-validation-example"></a>Form Validation Example

Enable a submit button only when the form is valid:

```java
ValueSignal<String> email = new ValueSignal<>("");
ValueSignal<String> password = new ValueSignal<>("");

Signal<Boolean> formValid = Signal.computed(() -> {
    String e = email.get();
    String p = password.get();
    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
```

## <a id="two-way-form-field-binding"></a>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:

```java
import com.vaadin.flow.signals.shared.SharedValueSignal;

ValueSignal<String> username = new ValueSignal<>("");

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

// User types "john" -> username.get() returns "john"
// username.set("jane") -> field displays "jane"
```

### <a id="supported-field-types"></a>Supported Field Types

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

```java
// Text fields
TextField textField = new TextField();
textField.bindValue(stringSignal, stringSignal::set);

TextArea textArea = new TextArea();
textArea.bindValue(stringSignal, stringSignal::set);

// Checkbox
ValueSignal<Boolean> accepted = new ValueSignal<>(false);
Checkbox checkbox = new Checkbox("Accept terms");
checkbox.bindValue(accepted, accepted::set);

// Number field
ValueSignal<Double> amount = new ValueSignal<>(0.0);
NumberField numberField = new NumberField("Amount");
numberField.bindValue(amount, amount::set);

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

// Date picker
ValueSignal<LocalDate> birthDate = new ValueSignal<>(null);
DatePicker datePicker = new DatePicker("Birth date");
datePicker.bindValue(birthDate, birthDate::set);
```

### <a id="read-only-binding"></a>Read-Only Binding

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

```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.set(true));
// After clicking: field becomes read-only
```

### <a id="required-indicator-binding"></a>Required Indicator Binding

Use `bindRequiredIndicatorVisible()` to control the required indicator on form fields:

```java
ValueSignal<Boolean> required = new ValueSignal<>(true);

TextField field = new TextField("Name");
field.bindRequiredIndicatorVisible(required);
```

For more details, see [Required Indicator Binding](https://vaadin.com/docs/latest/flow/ui-state/element-bindings.md#required-indicator-binding) in Element Bindings.

## <a id="binding-inline-styles"></a>Binding Inline Styles

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

```java
ValueSignal<String> backgroundColor = new ValueSignal<>("white");

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

// Change background color
backgroundColor.set("lightblue");
```

### <a id="dynamic-styling-example"></a>Dynamic Styling Example

Create dynamic visual feedback based on data:

```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")));
```

## <a id="binding-size"></a>Binding Size

Use `bindWidth()` and `bindHeight()` to bind a component’s size to signals. These are available on components that implement `HasSize`:

```java
ValueSignal<String> width = new ValueSignal<>("200px");
ValueSignal<String> height = new ValueSignal<>("100px");

Div panel = new Div();
panel.bindWidth(width);
panel.bindHeight(height);

// Resize the panel
width.set("400px");
height.set("250px");
```

## <a id="binding-placeholder"></a>Binding Placeholder

Use `bindPlaceholder()` to bind a component’s placeholder text to a signal. This is available on components that implement `HasPlaceholder`:

```java
ValueSignal<String> placeholder = new ValueSignal<>("Search...");

TextField searchField = new TextField();
searchField.bindPlaceholder(placeholder);

// Update placeholder text
placeholder.set("Type to filter results...");
```

## <a id="binding-helper-text"></a>Binding Helper Text

Use `bindHelperText()` to bind a component’s helper text to a signal. This is available on components that implement `HasHelper`:

```java
ValueSignal<Integer> remaining = new ValueSignal<>(100);

TextArea textArea = new TextArea("Comment");
textArea.bindHelperText(remaining.map(r -> r + " characters remaining"));
```

## <a id="binding-css-class-names"></a>Binding CSS Class Names

Use `bindClassName()` and `bindClassNames()` to bind CSS class names to signals. These are component-level convenience methods available on components that implement `HasStyle`:

```java
ValueSignal<Boolean> highlighted = new ValueSignal<>(false);

Div panel = new Div();

// Toggle a single class name based on a boolean signal
panel.bindClassName("highlighted", highlighted);
```

To bind a dynamic list of class names, use `bindClassNames()`:

```java
ValueSignal<List<String>> classes = new ValueSignal<>(List.of("card", "elevated"));

Div panel = new Div();
panel.bindClassNames(classes);

// Update all class names at once
classes.set(List.of("card", "flat", "bordered"));
```

> **Note:** These are convenience shortcuts for `getElement().getClassList().bind()`. For Element-level class bindings, see [ClassList Binding](https://vaadin.com/docs/latest/flow/ui-state/element-bindings.md#classlist-binding).

## <a id="binding-theme-names"></a>Binding Theme Names

Theme names apply the style variants a component supports, such as `small` on a button or `row-stripes` on a grid.

Use `bindThemeName()` and `bindThemeNames()` to bind theme names to signals. These are component-level convenience methods available on components that implement `HasTheme`:

```java
ValueSignal<Boolean> smallVariant = new ValueSignal<>(false);

Button button = new Button("Save");

// Toggle a single theme name based on a boolean signal
button.bindThemeName("small", smallVariant);
```

To bind a dynamic list of theme names, use `bindThemeNames()`:

```java
ValueSignal<List<String>> themes = new ValueSignal<>(List.of("row-stripes"));

Grid<Person> grid = new Grid<>();
grid.bindThemeNames(themes);

// Update all theme names at once
themes.set(List.of("row-stripes", "compact", "no-border"));
```

These methods are convenience shortcuts for `getThemeList().bind()`, the Element-level binding described in [ThemeList Binding](https://vaadin.com/docs/latest/flow/ui-state/element-bindings.md#themelist-binding). That one is available on every component, including those that don’t implement `HasTheme`. Each theme name binds independently:

```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);
```

> **Note:** Theme names are per-component style variants. They are not how an application switches between light and dark: use the `ColorScheme` annotation or `Page.setColorScheme()` for that, as described in [Light & Dark Color Schemes](https://vaadin.com/docs/latest/styling/themes.md#color-schemes).

## <a id="rendering-lists-of-components"></a>Rendering Lists of Components

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

```java
import com.vaadin.flow.signals.local.ListSignal;

ListSignal<String> items = new ListSignal<>();

VerticalLayout container = new VerticalLayout();

container.bindChildren(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
```

> **Note:** Use `ListSignal` for single-user scenarios (dynamic form fields, local selections). Use `SharedListSignal` when multiple users need to see the same list in real-time or when you need transactional guarantees. See [ListSignal](https://vaadin.com/docs/latest/flow/ui-state/local-signals.md#listsignal) for more details.

### <a id="complex-list-items"></a>Complex List Items

Create rich list items with nested bindings. This example uses `SharedListSignal` for a collaborative todo list where multiple users can see changes in real-time:

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

// Use SharedListSignal for multi-user collaboration
SharedListSignal<Todo> todos = new SharedListSignal<>(Todo.class);

VerticalLayout todoList = new VerticalLayout();

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

    // Two-way binding to the 'done' property using updater
    Checkbox checkbox = new Checkbox();
    checkbox.bindValue(todoSignal.map(Todo::done), todoSignal.updater(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 `updater()` method creates a write callback that enables direct two-way binding between the checkbox and the `done` property. See [Two-Way Signal Mapping](https://vaadin.com/docs/latest/flow/ui-state/local-signals.md#two-way-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.

### <a id="named-slot-children"></a>Named-Slot Children

The `bindChildren()` method manages only the default-slot children of the component. Named-slot children (those with a `slot` attribute) are not affected and can be added or removed freely while the binding is active. However, the component must not contain any default-slot children that are not part of the bound list.

### <a id="efficiency"></a>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

## <a id="binding-items-to-data-components"></a>Binding Items to Data Components

Use `Signal.effect()` and `ListSignal` to bind data items to components like `Grid`, `ComboBox`, or any component implementing `HasDataView` or `HasDataProvider`. This enables reactive updates to the data without manual data provider management:

```java
import com.vaadin.flow.signals.local.ListSignal;

ListSignal<String> items = new ListSignal<>();
items.insertLast("Item 1");
items.insertLast("Item 2");

Grid<String> grid = new Grid<>();
grid.addColumn(item -> item).setHeader("Name");

Signal.effect(grid, () -> grid.setItems(items.getValues().toList()));

// Adding or updating an item triggers a full refresh
items.insertLast("Item 3");
items.peek().get(0).set("Updated Item 1");
```

This pattern refreshes the entire data set by setting a new list of items when any change occurs to the list signal’s structure or an individual item signal. The framework doesn’t yet provide a dedicated helper for binding items to data components. Support for more efficient granular item updates and lazy loaded items bindings is planned in future releases.

This approach works with both `ListSignal` (for local, single-user scenarios) and `SharedListSignal` (for multi-user, real-time scenarios).

### <a id="difference-from-bindchildren"></a>Difference from bindChildren

Use `bindChildren()` when you want to create custom components for each item in a layout container (e.g., `VerticalLayout`). Use `Signal.effect()` with `setItems()` when you want to populate a data items component (e.g., `Grid`, `VirtualList`) that manages its own rendering.

## <a id="component-specific-bindings"></a>Component-Specific Bindings

In addition to the general-purpose bindings, some components provide binding methods for state that’s specific to that component. Like the other binding methods, they return a `SignalBinding` that can be used to register `onChange` callbacks. Two-way bindings accept a write callback as the second argument; pass `null` for one-way binding.

For example, bind the selection of a `Grid` to a signal through the single-select or multi-select wrapper:

```java
ValueSignal<Person> selectedPerson = new ValueSignal<>();

Grid<Person> grid = new Grid<>(Person.class);
grid.asSingleSelect().bindValue(selectedPerson, selectedPerson::set);

// Selecting a row updates the signal;
// setting the signal updates the selection
```

Other components expose their own bindings in the same style — for instance, `MessageList.bindItems()`, `AppLayout.bindDrawerOpened()`, `Checkbox.bindIndeterminate()`, and `bindThemeVariants()` on components that implement `HasThemeVariant`. See each component’s documentation for the component-specific bindings it offers.

## <a id="built-in-signal-sources"></a>Built-In Signal Sources

Vaadin provides several built-in signals that expose framework-level state reactively.

### <a id="window-size-signal"></a>Window Size Signal

Use `Page.windowSizeSignal()` to get a read-only signal that tracks the browser window size. The signal provides a `WindowSize` record with `width()` and `height()` values in pixels:

```java
Signal<WindowSize> windowSize = UI.getCurrentOrThrow().getPage().windowSizeSignal();

Span dimensions = new Span();
dimensions.bindText(windowSize.map(ws ->
    String.format("Window: %d x %d", ws.width(), ws.height())));

// Create a responsive layout
VerticalLayout content = new VerticalLayout();
content.bindClassName("mobile-layout", windowSize.map(ws -> ws.width() < 768));
```

> **Note:** The signal is lazily initialized. The actual window dimensions are available from the initial browser connection through extended client details, so the signal has correct values from the start. It also updates automatically on browser window resize.

### <a id="element-size-signal"></a>Element Size Signal (new in V25.3)

Use `Element.sizeSignal()` to get a read-only signal that tracks the size of a single element, as reported by the browser ResizeObserver API. The signal provides a `Size` record with `width()` and `height()` values in whole pixels:

```java
Div chartContainer = new Div();
Signal<Size> size = chartContainer.getElement().sizeSignal();

Span dimensions = new Span();
dimensions.bindText(size.map(s ->
    String.format("Container: %d x %d", s.width(), s.height())));

// Switch to a compact rendering when the container itself gets narrow
chartContainer.bindClassName("compact", size.map(s -> s.width() < 400));
```

Where the window size signal follows the browser window, this one follows the element, so it also reacts to size changes caused by the surrounding layout — a side navigation being collapsed, for example.

> **Note:** The signal is lazily initialized, and the same instance is returned for later calls on the same element, so one browser-side observer is shared. Its value is `Size(0, 0)` until the browser has reported the actual size, which happens shortly after the element has been attached. Sub-pixel sizes are rounded to whole pixels. While the element is detached there is nothing to observe, so the signal keeps the size last reported for it, and observation resumes when the element is attached again.

### <a id="ui-locale-signal"></a>UI Locale Signal

Use `UI.localeSignal()` to get a read-only signal that tracks the UI’s locale:

```java
Signal<Locale> locale = UI.getCurrentOrThrow().localeSignal();

Span greeting = new Span();
greeting.bindText(locale.map(loc ->
    loc.getLanguage().equals("fi") ? "Tervetuloa" : "Welcome"));
```

The signal cannot be written directly. To change the locale, use `UI.setLocale()`, which updates the signal and also notifies `LocaleChangeObserver` implementations. For two-way binding with a field component, pass `setLocale()` as the write callback:

```java
UI ui = UI.getCurrent();

ComboBox<Locale> languageSelect = new ComboBox<>("Language");
languageSelect.setItems(Locale.ENGLISH, Locale.of("fi"));
languageSelect.bindValue(ui.localeSignal(), ui::setLocale);
```

### <a id="session-locale-signal"></a>Session Locale Signal

Use `VaadinSession.localeSignal()` to get a read-only signal that tracks the session-level locale. UIs that have not had their locale explicitly overridden derive their locale from this signal:

```java
VaadinSession session = VaadinSession.getCurrent();
Signal<Locale> sessionLocale = session.localeSignal();
```

To change the session locale, use `VaadinSession.setLocale()`. As with the UI locale signal, the signal can be combined with `setLocale()` as the write callback for two-way binding:

```java
languageSelect.bindValue(session.localeSignal(), session::setLocale);
```

### <a id="router-state-signal"></a>Router State Signal (since V25.2)

Use `UI.routerStateSignal()` to get a read-only signal that tracks the UI’s navigation state. The signal provides a `RouterState` record with the current location, route parameters, active route chain, and navigation target. Its value updates whenever a navigation completes, making it useful for reactive breadcrumbs and menus:

```java
Signal<RouterState> routerState = UI.getCurrent().routerStateSignal();

Span currentPath = new Span();
currentPath.bindText(routerState.map(
    state -> "Current path: " + state.location().getPath()));
```

See [Observing Navigation State](https://vaadin.com/docs/latest/flow/routing/navigation.md#observing-navigation-state) for details.

## <a id="complete-example"></a>Complete Example

Here’s a complete example combining multiple binding types:

```java
public class TaskManager extends VerticalLayout {
    // State
    private final ListSignal<Task> tasks = new ListSignal<>();
    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.get().size());
    private final Signal<Long> completedCount = Signal.computed(() ->
        tasks.get().stream()
            .filter(t -> t.get().done())
            .count());

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

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

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

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

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

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

        taskList.bindChildren(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.get() || !taskSignal.get().done()));

            // Two-way binding to the 'done' property
            Checkbox doneCheckbox = new Checkbox();
            doneCheckbox.bindValue(taskSignal.map(Task::done), taskSignal.updater(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 `updater()` (see [Two-Way Signal Mapping](https://vaadin.com/docs/latest/flow/ui-state/local-signals.md#two-way-mapping))

- Conditional button enabling (`bindEnabled()`)

- Computed signals for derived values

- List rendering with `bindChildren()`

- Dynamic visibility (`bindVisible()`)

- Dynamic styling (`getStyle().bind()`)

> **Note:** If you want to share the task manager across users, use `SharedValueSignal` and `SharedListSignal` instead. See [Shared Signals](https://vaadin.com/docs/latest/flow/ui-state/shared-signals.md) for more details.
