> Markdown version of [Local Signals](https://vaadin.com/docs/next/flow/ui-state/local-signals). Section index: [llms.txt](https://vaadin.com/docs/next/flow/llms.txt)

# Local Signals

Local signals provide lightweight, UI-only state management for scenarios where state doesn’t need to be shared across users or sessions. They are ideal for managing component-level state like visibility toggles, form state, or local UI preferences.

## <a id="overview"></a>Overview

Local signals are scoped to a single UI session, providing a simple and efficient way to manage component-level state. Two types of local signals are available:

- `ValueSignal<T>` - holds a single value

- `ListSignal<T>` - holds an ordered list of values with per-entry reactivity

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

ValueSignal<String> name = new ValueSignal<>("Initial value");
ListSignal<String> items = new ListSignal<>();
```

## <a id="creating-local-signals"></a>Creating Local Signals

Local signals can be created with or without an initial value:

```java
// With initial value
ValueSignal<String> nameSignal = new ValueSignal<>("John");

// Without initial value (null)
ValueSignal<User> userSignal = new ValueSignal<>();
```

### <a id="custom-equality-checkers"></a>Custom Equality Checkers

By default, signals use `Objects.equals()` to determine whether a value has changed. You can provide a custom equality checker to control when updates are skipped:

```java
// Skip updates when strings are equal ignoring case
ValueSignal<String> name = new ValueSignal<>("John",
    (a, b) -> a != null && a.equalsIgnoreCase(b));

name.set("john"); // No update triggered — considered equal
name.set("Jane"); // Update triggered
```

For `ListSignal`, the equality checker is applied to all entry `ValueSignal` instances within the list:

```java
// All entries use the custom equality checker
ListSignal<String> items = new ListSignal<>(
    (a, b) -> a != null && a.equalsIgnoreCase(b));

ValueSignal<String> entry = items.insertLast("Hello");
entry.set("hello"); // No update triggered
```

## <a id="reading-and-writing-values"></a>Reading and Writing Values

Signals provide two methods for reading values:

- `get()` - reads the value and registers a reactive dependency. This must only be called inside a reactive context such as an effect, a computed signal, or a transaction. Calling `get()` outside a reactive context throws `IllegalStateException`.

- `peek()` - reads the current value without registering a dependency. Use this for one-time reads in click listeners, initialization code, or any code outside a reactive context.

Use `set()` to write a new value:

```java
ValueSignal<String> signal = new ValueSignal<>("Hello");

// Read the current value (outside reactive context)
String current = signal.peek();

// Write a new value
signal.set("World");
```

## <a id="updating-values"></a>Updating Values

The `update()` method allows you to update a value based on its current state:

```java
ValueSignal<Integer> counter = new ValueSignal<>(0);

// Update based on current value
counter.update(current -> current + 1);
```

This is particularly useful for updating records or beans where you need to create a new instance with modified properties:

```java
record Task(String text, TaskState state) {}

ValueSignal<Task> taskSignal = new ValueSignal<>(new Task("Write docs", TaskState.PENDING));

// Update task state by creating a new record with the modified value
taskSignal.update(t -> new Task(t.text(), TaskState.COMPLETED));
```

## <a id="listsignal"></a>ListSignal

`ListSignal<T>` provides an ordered list of values where each entry is independently reactive. Changes to the list structure (adding or removing items) trigger list-level dependents, while changes to individual entry values only trigger that entry’s dependents.

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

ListSignal<String> tags = new ListSignal<>();
```

### <a id="adding-items"></a>Adding Items

Use `insertFirst()`, `insertLast()`, or `insertAt()` to add items:

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

// Add to the end (most common)
ValueSignal<String> lastItem = items.insertLast("Last");

// Add to the beginning
ValueSignal<String> firstItem = items.insertFirst("First");

// Add at a specific position (0-indexed)
ValueSignal<String> middleItem = items.insertAt(1, "Middle");

// List is now: ["First", "Middle", "Last"]
```

Each insert method returns a `ValueSignal<T>` representing the entry. You can use this signal to update or remove the entry later.

### <a id="adding-multiple-items"></a>Adding Multiple Items (since V25.2)

Use `insertAllFirst()`, `insertAllLast()`, or `insertAllAt()` to insert several items in one operation. All entries are added with a single change notification, which is more efficient than inserting items one by one:

```java
ListSignal<String> items = new ListSignal<>();
items.insertLast("Existing");

// Insert at the end, preserving the collection order
List<ValueSignal<String>> added = items.insertAllLast(List.of("A", "B"));

// Insert at the beginning
items.insertAllFirst(List.of("First", "Second"));

// Insert at a specific index (0-indexed)
items.insertAllAt(1, List.of("X", "Y"));
```

Each method returns an unmodifiable list of `ValueSignal` instances for the inserted entries, in the same order as the provided collection.

### <a id="removing-items"></a>Removing Items

Use `remove()` to remove a specific entry, or `clear()` to remove all entries:

```java
ListSignal<String> items = new ListSignal<>();
ValueSignal<String> entry = items.insertLast("Item to remove");

// Remove a specific entry
items.remove(entry);

// Remove all entries
items.clear();
```

### <a id="reordering-items"></a>Reordering Items

Use `moveTo()` to move an entry to a new position within the list without removing and re-inserting it:

```java
ListSignal<String> items = new ListSignal<>();
ValueSignal<String> a = items.insertLast("A");
items.insertLast("B");
items.insertLast("C");
// List is: ["A", "B", "C"]

items.moveTo(a, 2);
// List is now: ["B", "C", "A"]
```

This is more efficient than removing and re-inserting, and preserves the entry’s signal identity. Components bound to the moved entry are not recreated — they are repositioned in the DOM.

### <a id="reading-list-contents"></a>Reading List Contents

Use `get()` to get a snapshot of the list:

```java
ListSignal<String> items = new ListSignal<>();
items.insertLast("A");
items.insertLast("B");

// Get a snapshot list of ValueSignal entries
List<ValueSignal<String>> entries = items.peek();

// Read values from entries
entries.forEach(entry -> System.out.println(entry.peek()));
```

### <a id="updating-entry-values"></a>Updating Entry Values

Since each list entry is a `ValueSignal`, you can update individual entries without affecting other entries or the list structure:

```java
ListSignal<String> items = new ListSignal<>();
ValueSignal<String> entry = items.insertLast("Original");

// Update the entry value
entry.set("Updated");

// The list still has one entry, but its value changed
```

This per-entry reactivity is efficient: only components bound to the changed entry update, not all components bound to the list.

### <a id="listsignal-example"></a>ListSignal Example

Here’s an example of dynamic phone number fields where users can add and remove entries:

```java
public class PhoneNumbersEditor extends VerticalLayout {
    private final ListSignal<String> phoneNumbers = new ListSignal<>();

    public PhoneNumbersEditor() {
        VerticalLayout phoneList = new VerticalLayout();
        phoneList.setPadding(false);
        phoneList.setSpacing(false);

        phoneList.bindChildren(phoneNumbers, phoneSignal -> {
            HorizontalLayout row = new HorizontalLayout();
            row.setAlignItems(FlexComponent.Alignment.CENTER);

            TextField phoneField = new TextField();
            phoneField.setPlaceholder("Phone number");
            phoneField.bindValue(phoneSignal, phoneSignal::set);

            Button removeButton = new Button(VaadinIcon.MINUS.create());
            removeButton.addClickListener(e -> phoneNumbers.remove(phoneSignal));

            row.add(phoneField, removeButton);
            return row;
        });

        Button addButton = new Button("Add phone number", e -> phoneNumbers.insertLast(""));
        add(phoneList, addButton);
    }
}
```

### <a id="when-to-use-listsignal-vs-sharedlistsignal"></a>When to Use ListSignal vs SharedListSignal

Use `ListSignal` when:

- The list is only relevant to a single user’s UI session

- You don’t need to synchronize the list across users or browser tabs

- Examples: dynamic form fields, local shopping cart UI, tabs/panels, temporary selections

Use `SharedListSignal` when:

- Multiple users need to see the same list in real-time

- You need transactional guarantees for list operations

- Examples: collaborative todo lists, shared document comments, live dashboards

## <a id="replacing-values"></a>Replacing Values

The `replace()` method performs a compare-and-set operation:

```java
ValueSignal<String> status = new ValueSignal<>("pending");

// Only replaces if current value matches expected
boolean replaced = status.replace("pending", "complete");
```

## <a id="read-only-signal-views"></a>Read-Only Signal Views

Use `asReadonly()` to create a read-only view of a `ValueSignal`. The returned `Signal` reflects all changes to the original signal, but cannot be written to:

```java
ValueSignal<String> editable = new ValueSignal<>("Hello");
Signal<String> readOnly = editable.asReadonly();

// Changes to the original are visible through the read-only view
editable.set("World");
// readOnly now returns "World"
```

This is useful for exposing signal values to other parts of the application without allowing modifications:

```java
public class UserService {
    private final ValueSignal<User> currentUser = new ValueSignal<>(null);

    // Expose a read-only signal to the UI
    public Signal<User> currentUser() {
        return currentUser.asReadonly();
    }

    // Only the service can modify the signal
    public void login(User user) {
        currentUser.set(user);
    }
}
```

## <a id="working-with-mutable-values"></a>Working with Mutable Values

For mutable values, use the `modify(Consumer)` method to perform updates. This method ensures the signal properly tracks changes:

```java
public class User {
    private String name;
    private int age;

    // getters and setters...
}

ValueSignal<User> userSignal = new ValueSignal<>(new User("Jane", 25));

// Correct: Use modify() for mutable objects
userSignal.modify(user -> user.setAge(26));

// Incorrect: Direct mutation won't trigger reactivity
User user = userSignal.peek();
user.setAge(27); // This change won't be detected!
```

> **Warning:** Direct mutation of objects retrieved from a signal won’t trigger reactive updates. Always use `modify()` for mutable values, or preferably use immutable types like Java records.

## <a id="thread-safety"></a>Thread Safety

Local signals are thread-safe and can be updated from any thread without wrapping calls in `ui.access()`. The signal handles UI synchronization internally:

```java
public class MyView extends VerticalLayout {
    private final ValueSignal<String> localState = new ValueSignal<>("");

    public MyView() {
        TextField field = new TextField();
        field.bindValue(localState, localState::set);

        Span output = new Span();
        output.bindText(localState);
        add(field, output);
    }
}
```

> **Note:** When updating signals from background threads, ensure your application has the `@Push` annotation enabled. This allows the server to push UI updates to the client when signal values change asynchronously.

However, local signals are scoped to a single server instance. Vaadin doesn’t yet provide an integrated solution for sharing local signals within a cluster. If you need state that works across a cluster or is shared between users, use [Shared Signals](https://vaadin.com/docs/next/flow/ui-state/shared-signals.md) instead.

## <a id="transaction-limitations"></a>Transaction Limitations

Local signals (`ValueSignal` and `ListSignal`) cannot participate in signal transactions:

```java
ValueSignal<String> localValue = new ValueSignal<>("value");
ListSignal<String> localList = new ListSignal<>();

// This will throw an exception
Signal.runInTransaction(() -> {
    localValue.set("new value"); // Not allowed!
    localList.insertLast("item");  // Not allowed!
});
```

Use shared signals if you need transactional guarantees. See [Transactions](https://vaadin.com/docs/next/flow/ui-state/transactions.md) for more information about signal transactions.

## <a id="peeking-values"></a>Peeking Values

Use `peek()` to read a value without registering a dependency in effects or computed signals:

```java
ValueSignal<String> signal = new ValueSignal<>("Hello");

Signal.effect(component, () -> {
    // peek() doesn't register a dependency
    String peeked = signal.peek();
    // This effect won't re-run when signal changes
});
```

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

Use the `map()` method to create a computed signal that transforms the original value:

```java
ValueSignal<String> name = new ValueSignal<>("john");
Signal<String> upperName = name.map(String::toUpperCase);

// upperName.get() returns "JOHN"
// When name changes, upperName automatically updates
```

Use `map()` for single-signal transformations. For transformations depending on multiple signals, use `Signal.computed()` instead.

## <a id="two-way-mapping"></a>Two-Way Signal Mapping

The read-only `map()` method described above creates a derived signal that updates when the source changes, but changes to the derived signal don’t propagate back. For two-way binding scenarios where you need changes to flow in both directions, use `bindValue()` with separate getter and setter arguments.

This is particularly useful when binding a form field to a property of a complex object stored in a signal. For example, binding a checkbox directly to the `done` property of a `Todo` record.

### <a id="mapping-immutable-values-records"></a>Mapping Immutable Values (Records)

For immutable values like Java records, use `map()` for reading and `updater()` for writing:

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

ValueSignal<Todo> todoSignal = new ValueSignal<>(new Todo("Write docs", false));

// Bind checkbox directly to the 'done' property
Checkbox checkbox = new Checkbox();
checkbox.bindValue(todoSignal.map(Todo::done), todoSignal.updater(Todo::withDone));
// Checking the box updates the todo's done property
// Calling todoSignal.update(...) updates the checkbox
```

The `updater()` method takes a wither function that receives the current parent value and the new child value, and returns a new parent value with the child property updated. Using wither methods like `withDone()` is a common pattern for records.

### <a id="mapping-mutable-values-beans"></a>Mapping Mutable Values (Beans)

For mutable beans with setters, use `map()` for reading and `modifier()` for writing:

```java
public class User {
    private String name;
    private int age;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

ValueSignal<User> userSignal = new ValueSignal<>(new User());

// Bind text field directly to the 'name' property
TextField nameField = new TextField("Name");
nameField.bindValue(userSignal.map(User::getName), userSignal.modifier(User::setName));
```

The `modifier()` method takes a setter function that receives the parent object and the new value, and mutates the parent in place. The signal framework handles change notification automatically.

> **Note:** Prefer immutable records over mutable beans when possible. Records are easier to reason about, naturally thread-safe, and work better with reactive patterns. Use `modifier()` only when working with existing mutable bean classes.

### <a id="comparison-read-only-vs-two-way-mapping"></a>Comparison: Read-Only vs Two-Way Mapping

| Method                                                   | Use Case                              |
| -------------------------------------------------------- | ------------------------------------- |
| `map(getter)`                                            | Read-only transformations             |
| `bindValue(signal.map(getter), signal.updater(merger))`  | Two-way binding with immutable values |
| `bindValue(signal.map(getter), signal.modifier(setter))` | Two-way binding with mutable beans    |

## <a id="organizing-signals-as-fields"></a>Organizing Signals as Fields

Storing signals as class fields is recommended for better code organization. It keeps all reactive state together at the top of the class, making it easier to understand the component’s state at a glance:

```java
public class MyView extends VerticalLayout {
    // State is clearly visible at the top of the class
    private final ValueSignal<String> nameSignal = new ValueSignal<>("");
    private final Signal<String> greeting = nameSignal.map(n -> "Hello, " + n);

    public MyView() {
        // Components can be local variables since they're added to the layout
        Span greetingSpan = new Span();
        greetingSpan.bindText(greeting);
        add(greetingSpan);
    }
}
```

This pattern also allows computed signals to be defined declaratively alongside their source signals, making the reactive dependencies clear.

## <a id="use-cases"></a>Use Cases

Local signals are ideal for:

**ValueSignal use cases:**

- **UI toggle states**: Panel expansion, modal visibility, sidebar open/closed

- **Form state**: Current step in a wizard, validation state

- **Local filters**: Search text, sort order within a single view

- **Temporary state**: Editing mode, selection state

```java
public class ExpandablePanel extends VerticalLayout {
    private final ValueSignal<Boolean> expanded = new ValueSignal<>(false);

    public ExpandablePanel(String title, Component content) {
        Button header = new Button(title);
        header.addClickListener(e -> expanded.update(v -> !v));

        // Reactively show/hide content
        content.bindVisible(expanded);

        add(header, content);
    }
}
```

**ListSignal use cases:**

- **Dynamic form fields**: Add/remove phone numbers, addresses, or other repeating fields

- **Local shopping cart**: Managing cart items in a single-user session

- **UI tabs or panels**: Dynamic tabs that users can open and close

- **Temporary selections**: Multi-select items before batch operations
