From vaadin-development
Guide Claude on using Vaadin Signals for reactive state management in Vaadin 25 Flow. This skill should be used when the user asks to "use signals", "manage state reactively", "share state between users", "use reactive state", "use ValueSignal", "use NumberSignal", "use ListSignal", "use computed signals", "use ComponentEffect", "bind signals to components", or needs help with reactive UI updates, signal transactions, signal factories, or thread-safe state management in Vaadin Flow.
npx claudepluginhub marcushellberg/vaadin-development-plugin --plugin vaadin-developmentThis skill uses the workspace's default tool permissions.
Use the Vaadin MCP tools (`search_vaadin_docs`) to look up the latest documentation whenever uncertain about a specific API detail. Always set `vaadin_version` to `"25"` and `ui_language` to `"java"`.
Guides Angular Signals usage for fine-grained reactive state management and zone-less change detection in Angular 16+ applications.
Guides Angular state management with Signals, NgRx, RxJS for local, global, server state. Use for setup, component stores, solution selection, debugging, migrations.
Manages reactive state in Angular 17+ using signal(), computed(), effect(), and toSignal() for fine-grained, zone-free reactivity without manual subscriptions.
Share bugs, ideas, or general feedback.
Use the Vaadin MCP tools (search_vaadin_docs) to look up the latest documentation whenever uncertain about a specific API detail. Always set vaadin_version to "25" and ui_language to "java".
Signals are a reactive state management system for Vaadin Flow. A signal holds a value, and when that value changes, all dependent parts of the UI automatically update — without manually adding and removing change listeners.
Key properties:
ValueSignal<String> name = new ValueSignal<>(String.class);
name.value("John"); // set value
String current = name.value(); // get value
name.update(n -> n + " Doe"); // atomic update based on current value
NumberSignal counter = new NumberSignal();
counter.value(5); // set
counter.incrementBy(1); // atomic increment
counter.incrementBy(-2); // atomic decrement
int count = counter.valueAsInt();
ListSignal<Person> people = new ListSignal<>(Person.class);
people.insertFirst(new Person("Jane", 25));
people.insertLast(new Person("John", 30));
List<ValueSignal<Person>> list = people.value();
list.get(0).value(new Person("Updated", 26)); // update individual item
MapSignal<String> config = new MapSignal<>(String.class);
config.put("theme", "dark");
config.putIfAbsent("language", "en");
Map<String, ValueSignal<String>> map = config.value();
For state used by only one UI instance (not shared). No thread safety overhead, but also no cross-user sharing:
ReferenceSignal<User> userSignal = new ReferenceSignal<>();
userSignal.value(new User("Jane", 25));
userSignal.modify(user -> user.setAge(26)); // mutable update allowed
ReferenceSignal cannot be used in transactions.
Effects are callbacks that re-run automatically when any signal they read changes. They are the bridge between signals and the UI.
ComponentEffect.effect(span, () -> {
span.setText(firstName.value() + " " + lastName.value());
});
The effect:
ComponentEffect.bind(label, nameSignal, Span::setText);
ComponentEffect.bind(button, enabledSignal, Button::setEnabled);
ComponentEffect.bind(div, visibleSignal, Div::setVisible);
Equivalent to ComponentEffect.effect but more concise for single-signal, single-property bindings.
Computed signals derive their value from other signals and update automatically:
Signal<String> fullName = Signal.computed(() -> {
return firstName.value() + " " + lastName.value();
});
Shorthand for a computed that depends on exactly one signal:
Signal<String> ageCategory = ageSignal.map(age ->
age < 18 ? "Child" : (age < 65 ? "Adult" : "Senior")
);
Use SignalFactory to create signal instances. The factory determines how signals are scoped:
Same signal instance for the same name within the same JVM. Use for state visible to all users:
NumberSignal globalCounter = SignalFactory.IN_MEMORY_SHARED.number("counter");
ValueSignal<String> sharedMessage = SignalFactory.IN_MEMORY_SHARED.value("msg", String.class);
NodeSignal sharedData = SignalFactory.IN_MEMORY_SHARED.node("data");
Always creates a new instance. Use for component-local state:
NumberSignal localCounter = SignalFactory.IN_MEMORY_EXCLUSIVE.number("counter");
Group multiple signal updates into an atomic operation. Observers see either all changes or none:
Signal.runInTransaction(() -> {
firstName.value("John");
lastName.value("Doe");
age.value(30);
});
Signals can bind directly to Element properties, attributes, text, classes, and styles:
Signal<String> text = counter.map(v -> String.format("Count: %.0f", v));
span.getElement().bindText(text);
span.getElement().bindProperty("hidden", hiddenSignal);
Supports String, Boolean, Double, JSON, Object (bean), List, and Map types.
span.getElement().bindAttribute("hidden",
hiddenSignal.map(h -> h ? "" : null));
span.getElement().getClassList().bind("active", isActiveSignal);
span.getElement().getStyle().bind("color", colorSignal);
Important: while a signal is bound to an element property, manual changes to that property throw BindingActiveException. Unbind first with bindText(null), bindProperty(null), etc.
public class SharedCounter extends VerticalLayout {
private final NumberSignal counter =
SignalFactory.IN_MEMORY_SHARED.number("counter");
public SharedCounter() {
Button button = new Button();
button.addClickListener(click -> counter.incrementBy(1));
add(button);
ComponentEffect.effect(button,
() -> button.setText(
String.format("Clicked %.0f times", counter.value())));
}
}
All users see the same counter value, and clicking in any browser updates all connected UIs (requires Push to be enabled).
Use immutable values — Strings, primitives, Java Records. Mutating an object directly won't trigger reactivity. Always create a new value:
// GOOD: new immutable object
user.update(u -> new User(u.getName(), u.getAge() + 1));
// BAD: mutating in place (won't trigger updates!)
User u = user.value();
u.setAge(u.getAge() + 1);
Use ComponentEffect.effect for UI bindings — it handles lifecycle (attach/detach) automatically, preventing memory leaks.
Use ComponentEffect.bind for simple one-signal bindings — cleaner than writing a full effect for label.setText(signal.value()).
Use transactions for multi-signal updates — prevents observers from seeing partial state.
Use update() for atomic read-modify-write — counter.update(c -> c + 1) is atomic; reading and then setting is not.
Don't modify signals inside effects or computed callbacks — they run in read-only transactions. If you must, use Signal.runWithoutTransaction(), but be very careful about infinite loops.
Use peek() to read without tracking — inside an effect, signal.peek() reads the value without creating a dependency, so the effect won't re-run when that signal changes.
Enable Push for shared signals — when using IN_MEMORY_SHARED, enable server push so changes propagate immediately to all connected UIs.
Prefer ReferenceSignal for single-UI mutable state — no thread-safety overhead when state doesn't need to be shared.