Signals in JavaScript: The TC39 Proposal That Could Bring Native Reactivity to the Language
Hello HaWkers, a proposal gaining momentum at TC39 could fundamentally change how we build reactive interfaces in JavaScript. Signals, a reactivity primitive already used by Angular, Vue, Solid, and Svelte, is being considered for inclusion in the official ECMAScript specification.
If you work with modern frontend, this proposal deserves your attention. Let's understand what Signals are, how the proposal works, and what this means for the future of web development.
What Are Signals
Signals are reactivity primitives that allow creating values that automatically notify dependents when they change. Think of them as smart variables that know who is using them.
The Basic Concept
The fundamental idea is simple but powerful:
// Illustrative pseudocode of the concept
const count = signal(0); // Creates a signal with initial value 0
const doubled = computed(() => count.value * 2); // Automatically derived
console.log(doubled.value); // 0
count.value = 5; // Changes the signal
console.log(doubled.value); // 10 - automatically updatedWhy Signals Matter
Problems that Signals solve:
Granular updates: Instead of re-rendering entire components, only what depends on the value changes
Automatic synchronization: Derived values are always consistent with their sources
Predictable performance: The system knows exactly what needs to be updated
Simpler mental model: Fewer concepts than systems like Redux
The TC39 Proposal
The Signals proposal for JavaScript is being developed collaboratively among representatives from various frameworks.
Who Is Involved
Proposal authors:
- Rob Eisenberg (Microsoft/Angular)
- Daniel Ehrenberg (Bloomberg/Igalia)
- Contributions from Vue, Solid, Svelte maintainers
Stated objective:
"Create a common reactivity foundation that enables interoperability between frameworks and reduces code duplication."
What The Proposal Includes
The proposal defines two main primitives:
Signal.State:
// Basic signal that holds a value
const counter = new Signal.State(0);
// Reading the value
console.log(counter.get()); // 0
// Writing the value
counter.set(counter.get() + 1);
console.log(counter.get()); // 1Signal.Computed:
const firstName = new Signal.State("John");
const lastName = new Signal.State("Doe");
// Computed that derives from other signals
const fullName = new Signal.Computed(() => {
return `${firstName.get()} ${lastName.get()}`;
});
console.log(fullName.get()); // "John Doe"
firstName.set("Jane");
console.log(fullName.get()); // "Jane Doe" - automatically updatedWhat Is NOT In The Proposal
Important to understand the limits:
Not included:
- Effects system (side effects)
- Automatic batching
- DOM integration
- Update scheduling
Why?
"The proposal is intentionally minimal to allow frameworks to implement their own semantics on the common base."
How Frameworks Already Use Signals
To understand the proposal, it's useful to see how existing frameworks implement reactivity.
Angular Signals
Angular officially adopted Signals in recent versions:
import { signal, computed, effect } from '@angular/core';
@Component({
template: `
<button (click)="increment()">
Count: {{ count() }}
</button>
<p>Double: {{ doubleCount() }}</p>
`
})
export class CounterComponent {
count = signal(0);
doubleCount = computed(() => this.count() * 2);
increment() {
this.count.update(value => value + 1);
}
constructor() {
effect(() => {
console.log(`Count changed to: ${this.count()}`);
});
}
}Vue Reactivity
Vue 3 uses a similar system called refs:
import { ref, computed, watchEffect } from 'vue';
const count = ref(0);
const doubled = computed(() => count.value * 2);
watchEffect(() => {
console.log(`Count is now: ${count.value}`);
});
// In template: {{ count }} and {{ doubled }}SolidJS Signals
SolidJS pioneered signals for the frontend:
import { createSignal, createEffect, createMemo } from 'solid-js';
function Counter() {
const [count, setCount] = createSignal(0);
const doubled = createMemo(() => count() * 2);
createEffect(() => {
console.log(`Count changed to: ${count()}`);
});
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count()} | Doubled: {doubled()}
</button>
);
}
Benefits of Standardization
Why include Signals in the language instead of leaving things as they are?
Interoperability Between Frameworks
Currently, signals from different frameworks are incompatible:
Current problem:
- An Angular signal doesn't work in Vue
- Components from different frameworks don't share state
- Libraries need to choose which system to support
With standardization:
- Common base would enable interoperability
- Libraries could work with any framework
- Migration between frameworks would be simpler
Bundle Size Reduction
Each framework loads its own reactivity implementation:
Current implementation sizes:
- Angular Signals: ~8KB
- Vue Reactivity: ~12KB
- SolidJS: ~7KB
- Preact Signals: ~3KB
With native implementation:
- Zero additional KB (part of runtime)
- Engine optimizations possible
- Potentially better performance
Unified Mental Model
Developers learn the concept once:
Currently:
- Each framework has different nomenclature
- APIs vary (get/set vs .value vs function)
- Subtly different behaviors
With standard:
- One concept, one API
- Centralized documentation
- Simplified knowledge transfer
Challenges and Controversies
The proposal is not unanimous. There are important debates in the community.
The React Debate
React doesn't use Signals internally:
React team position:
- Prefer immutability model
- Signals may encourage mutation
- Additional complexity for React use cases
Counterargument:
- React could implement layer over Signals
- Not mandatory to use, just available
- Other patterns also exist (classes, closures)
Specification Complexity
Some argue that Signals are too opinionated for the language:
Concerns:
- JavaScript never had UI primitives before
- May encourage specific patterns
- Engine implementation is complex
Authors' response:
- Signals are useful beyond UI (observables, data sync)
- Proposal is minimal and extensible
- Complex primitives already exist (Proxy, WeakMap)
Performance In Extreme Cases
Reactive systems can have overhead:
Potential problems:
- Dependency tracking has cost
- Many signals = lots of memory
- Update cascades
Proposed mitigations:
- Lazy evaluation by default
- Notification batching
- Weak references for cleanup
Practical Example: Implementing A Form
See how native Signals could work in a real scenario:
// With the proposal, it would be something like this:
const form = {
email: new Signal.State(''),
password: new Signal.State(''),
confirmPassword: new Signal.State('')
};
// Derived validations
const emailValid = new Signal.Computed(() => {
const email = form.email.get();
return email.includes('@') && email.includes('.');
});
const passwordsMatch = new Signal.Computed(() => {
return form.password.get() === form.confirmPassword.get();
});
const formValid = new Signal.Computed(() => {
return emailValid.get() &&
form.password.get().length >= 8 &&
passwordsMatch.get();
});
// Usage in fictional framework
function renderForm() {
return html`
<form>
<input
type="email"
value=${form.email.get()}
oninput=${e => form.email.set(e.target.value)}
class=${emailValid.get() ? 'valid' : 'invalid'}
/>
<input
type="password"
value=${form.password.get()}
oninput=${e => form.password.set(e.target.value)}
/>
<input
type="password"
value=${form.confirmPassword.get()}
oninput=${e => form.confirmPassword.set(e.target.value)}
class=${passwordsMatch.get() ? '' : 'error'}
/>
<button disabled=${!formValid.get()}>
Submit
</button>
</form>
`;
}
Timeline and Current Status
Where the proposal is in the TC39 process.
Current Stage
Status: Stage 1 (Proposal)
This means:
- The committee agrees the problem is worth solving
- The proposal is being actively developed
- Significant changes are still possible
- Not ready for implementation
Next Steps
For Stage 2:
- Initial complete specification
- Well-defined semantics
- Example implementation
For Stage 3:
- Implementation in at least one engine
- Real-world usage feedback
- Stable specification
For Stage 4:
- Multiple implementations
- Conformance tests
- Final approval
Conservative Prediction
Estimated timeline:
- 2026: Proposal refinement
- 2027: Possible Stage 2/3
- 2028+: Browser implementations
- 2029+: Safe production use
How To Prepare
Even if the proposal takes time, you can prepare.
Learn Signals Now
Use one of the existing implementations:
// Preact Signals - very similar to the proposal
import { signal, computed, effect } from '@preact/signals';
const count = signal(0);
const doubled = computed(() => count.value * 2);
effect(() => {
console.log(doubled.value);
});Understand The Fundamentals
Concepts that transfer to any implementation:
- Automatic dependency tracking
- Lazy vs eager evaluation
- Batching and scheduling
- Cleanup and lifecycle
Final Reflection
The Signals proposal at TC39 represents a possible fundamental evolution of JavaScript for reactive applications. Unifying how frameworks handle reactivity could simplify the ecosystem and improve interoperability.
Key points:
- Signals are reactivity primitives already proven in frameworks
- The TC39 proposal seeks standardization, not framework replacement
- Benefits include interoperability and code reduction
- Timeline is long - probably 2028+ for production use
- Learning signals now prepares you for the future
Regardless of when (or if) the proposal is approved, understanding signal-based reactivity is already valuable today. Angular, Vue, Solid, and other frameworks have already adopted the pattern, and the trend continues to grow.
If you want to explore more about modern JavaScript features, I recommend checking out another article: ES2026: The New Features That Will Change Your Code where you will discover other important proposals in progress.

