Back to blog

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 updated

Why Signals Matter

Problems that Signals solve:

  1. Granular updates: Instead of re-rendering entire components, only what depends on the value changes

  2. Automatic synchronization: Derived values are always consistent with their sources

  3. Predictable performance: The system knows exactly what needs to be updated

  4. 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()); // 1

Signal.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 updated

What 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.

Let's go! 🦅

Comments (0)

This article has no comments yet 😢. Be the first! 🚀🦅

Add comments