Signals and Signals. And Retained UI.
Introduction
Over the last few years, the word Signals has spread very quickly. But it often points to two different things at the same time.
One meaning is the narrow one: signals as runtime reactive primitives, like Vue's ref() or Solid's createSignal(), which hold values and dependency relationships at runtime.
The other meaning is larger: signals as a rendering context that includes DOM tracking itself, as in Svelte, Solid, and Vue Vapor.
I think these two should be separated deliberately. The former is reactivity of values. The latter is reactivity of the renderer.
If we blur them together, we get lazy conclusions like Signals = the end of VDOM.
But that is not what the ecosystem actually says.
The TC39 Signals proposal explicitly says in its FAQ that signals can work with VDOM, with the native DOM, or with a combination of both.
So Signals are not a rendering strategy by themselves.
To talk about this properly, we need to go back a little.
Knockout.js and the Observable era
Whenever I see the current signals discourse, I think of Knockout.js.
Knockout had observable and computed very early, with automatic dependency tracking.
And the Knockout docs explain something crucial: declarative bindings themselves are implemented as computed observables.
That matters. A lot of what we now call "signal-like" is not actually new. Wrapping values, tracking dependencies, and updating part of the UI when those values change has been around for a long time.
For a while, though, the standards conversation focused less on signals and more on observables. The original TC39 Observable proposal tried to standardize push-based streams of multiple values, such as DOM events, timers, and sockets. Today the work continues as the WICG Observable proposal, and that repository explicitly documents the proposal's history: the 2015 TC39 attempt, the 2017 WHATWG DOM discussion, and the 2019 revival attempt.
It helps to draw a line here:
- Observables mainly model event streams over time.
- Signals mainly model current values plus a dependency graph.
They are related, but not identical. Observables are primarily temporal. Signals are primarily current-value oriented.
That is why the history of Observable standardization and the current Signals proposal are connected, but not interchangeable.
It is also notable that the Signals proposal is explicitly centered less on a developer-facing API and more on the core semantics of an underlying signal graph shared by frameworks. Even here, Signals are already being framed as a substrate beneath frameworks, not merely as a userland convenience API.
Signals and Signals.
This is the core distinction I want to make.
The word "signals" is being used at at least two different scopes.
1. Signals as runtime primitives
This is the narrower meaning.
Vue's reactivity docs explain that in Vue 3, reactive() uses Proxies while ref() uses getters and setters to implement track() and trigger().
The same page also states that Vue's reactivity system is primarily runtime-based.
Solid's createSignal() docs describe a primitive where the getter tracks dependencies inside a reactive context and the setter notifies dependent computations.
At this layer, signals are something like this:
signal cell -> track reads -> trigger writes -> rerun effects
The main character here is the value. Which value was read? Which effect depends on it? What should be re-executed after mutation?
Vue ref and Solid createSignal are both easy to understand in this sense.
2. Signals as a rendering architecture
But there is another meaning.
Svelte describes itself as a UI framework that uses a compiler so the browser does minimal work. The FAQ in the TC39 Signals proposal also points to Svelte 5 as an example where runes are transformed into an internal signals library. The Solid README shows compiled output that creates real DOM nodes and updates only the exact insertion point instead of rerendering the whole button. And Solid's fine-grained reactivity docs explicitly contrast targeted updates in Solid with broader component re-execution in React.
At that point, signals are no longer just value containers. They become part of a context that also includes DOM-side tracking: which signal feeds which text node, which attribute, which exact sink in the rendered output.
signal graph -> compiler-known DOM sinks -> exact DOM mutation
Now part of the renderer's job has been absorbed into the dependency graph itself. The re-execution boundary is no longer naturally "the whole component"; it gets much closer to the concrete DOM sink.
In this sense, "signals" is not just the name of a primitive.
It is the name of an architectural stance.
Here I repeat the same word, Signals.
Retained UI
So where do VDOM and React Fiber fit?
I think of them as Retained UI. That is: a runtime that first keeps the desired UI as nodes, frames, or trees in memory, and then manages updates from there.
React's Render and Commit docs explain the sequence clearly:
- render calls components,
- commit applies the result to the DOM,
- and on re-render React computes what changed and applies only the minimal necessary DOM operations.
Then React Fiber architecture goes deeper:
- rendering generates a tree in memory,
- a new tree is diffed against the previous one,
- Fiber is a unit of work, effectively a virtual stack frame specialized for React.
This is the important part. In React, the primary structure is not a signal dependency graph. It is the in-memory tree / fiber graph. State updates are the trigger, but the actual responsibility for DOM mutation belongs to the reconciler and the renderer.
In other words:
state update -> rerun components -> new in-memory tree -> reconcile -> commit DOM
That does not mean React has no reactivity. It means reactivity is not foregrounded as a first-class dependency graph in the same way it is in Vue or Solid. In React, reactivity mostly shows up as a question of which component subtree must be reevaluated, while Retained UI remains the main update engine.
Vue has long sat in between
What makes Vue interesting is that it has long contained both layers.
Vue's Rendering Mechanism docs explain that Vue:
- compiles templates into render functions,
- performs mount as a reactive effect,
- reruns that effect when dependencies change to create a new VDOM tree,
- patches the DOM through reconciliation.
The same page also explains compiler-informed virtual DOM, including patch flags and tree flattening.
So Vue has long had:
- a lower layer of reactivity via
ref,reactive, and effects, - and an upper layer of compiler-assisted VDOM runtime.
That is not merely "between React and Solid". It is more precise to say that Vue has long combined Signals and Retained UI in one system.
This is why Vue makes the bigger point visible: signals do not inherently negate VDOM. Signals can sit beneath VDOM. Or they can sit beneath a more direct DOM-oriented renderer.
And again, the TC39 Signals proposal says exactly this: signals can back VDOM frameworks, native DOM frameworks, or hybrids.
The future of Vue Vapor
From this perspective, Vue Vapor becomes much easier to read.
The release note for Vue 3.6.0-beta.1, published on December 23, 2025, describes Vapor Mode as:
- a new compilation mode for Vue SFCs,
- aimed at reducing baseline bundle size and improving performance,
- 100% opt-in,
- feature-complete for the intended subset, but still unstable.
But the really important details are the interop details. That same release note says:
- Vapor-only mode does not support Suspense directly, but Vapor components can be rendered inside a VDOM Suspense
- a VDOM app created with
createAppcan use Vapor components throughvaporInteropPlugin - a Vapor app can also use VDOM components through the same interop path
- mixed nesting still has rough edges, so Vue recommends distinct regions
That is very revealing. At least in the near future, Vue Vapor is not "the next Vue that fully replaces VDOM." It looks much more like a high-performance subset that grows through interoperability with the existing Vue runtime.
The public Vapor Roadmap reinforces this. It includes work around:
- SSR / Hydration
- Template Ref Interop
- Suspense
- Vue Router
- Pinia
- Nuxt.js
- DevTools Integration
- Vue Test Utils
That tells us something simple. The fundamental challenge of Vapor is not merely "can it update the DOM faster?" The challenge is how it fits the Vue ecosystem as a whole.
So my reading of Vue Vapor's future is this:
- VDOM is not just leftover legacy weight
- Vapor becomes the high-performance subset for perf-sensitive paths
- the VDOM runtime remains important for interop, ecosystem breadth, and library compatibility
- and the boundary between the two becomes more explicit over time
That feels very Vue-like to me. Vue has always cared about progressive and incremental adoption. If Vapor succeeds, I suspect it will succeed less by replacing everything and more by establishing better architectural boundaries.
Why this matters in comparison with React
In React, Retained UI is closer to the core than value-level reactivity. Fiber handles scheduling, reconciliation, prioritization, and work management.
By contrast, the direction of Vapor, Svelte, and the Solid compiler is to bring dependency tracking and DOM sink tracking closer together, reducing how much they need an in-memory tree as the primary update mechanism.
The comparison should not be "which one wins." The real comparison is: which layer owns update responsibility?
- React / classic VDOM: the in-memory node runtime owns updates
- runtime signals: the signal graph owns invalidation, but the renderer is still separate
- compiler + DOM-tracker signals: the signal graph begins to absorb part of the renderer's responsibility
These are not the same thing.
And Vue, right now, is trying to carry all of them in one system.
ref remains.
VDOM mode remains.
And Vapor grows on top of that.
I think that is a healthy direction.
Signals are not a magic silver bullet.
And VDOM is not merely a relic of the past.
Retained UI is still strong at component composition, dynamic rendering, ecosystem interop, debuggability, and tooling alignment.
At the same time, compiler-guided Signals. are strong on fine-grained hot-path updates and baseline bundle size.
The question is almost never "which one should disappear?" The real question is "where should the boundary be?"
This is a personal essay, not an official position of the Vue Team, the React Team, or TC39.