Stage: 1 (as of the Jan 2026 plenary)
Author/champion: Lea Verou (@leaverou)
For history, see the original proposal.
The vast majority (possibly over 90%, though it's hard to prove) of accessor use cases are additive. Their conceptual model is not entirely arbitrary logic, but a layering of transformations, side effects, and access control over a regular property (public or private).
In some cases, this property is an internal implementation detail, and is never accessed separately. These cases are well served by auto-accessors together with built-in decorators.
However, when a pre-existing property (often deeply nested) is used as the backing store, authors need to fall back to the usual boilerplate.
class C {
#foo = new Signal(1);
get foo () {
return this.#foo.value;
}
set foo (value) {
this.#foo.value = value;
}
}This proposal explores potential syntaxes with higher signal-to-noise ratio for these use cases, such as:
class C {
#foo = new Signal(1);
alias foo to #foo.value;
}Some (not mutually exclusive) high-level use cases for this are:
- Encapsulation: obscure the data source so it can be changed later
- Ergonomics: shorten frequently accessed property chains
- Composition: Selectively expose API surface from other objects or first-class protocols
- Access control: private properties with public getters
Anecdotally:
I'm looking at my accessors usage, and they are (numbers are not measured):
- 75% "property forwarding"
- 15% lazy initial computation
- 10% validation
- 5% other
— Nicolò Ribaudo (@nicolo-ribaudo)
Private fields as targets is a very important use case, and there is currently no way to pass them around separately from the object they are defined on.
But even for public fields, a decorator would be pretty awkward:
class C {
// But we're not setting this to an array!
@alias accessor foo = ["a", "b", "c"];
}class C {
// Better, but still confusing
@alias(["a", "b", "c"]) accessor foo;
}Perhaps with something like reference declarations or similar it may become palatable:
class C {
@alias accessor foo = x => ref x.a.#b.c;
}or
class C {
@alias(x => ref x.a.#b.c) accessor foo;
}But it's still far from ideal.
Just like auto-accessors, if no side effects or logic is desired, proxying another property should involve little additional syntax over the property name and a reference to the property (chain) holding the underlying value. It could even be a separate type of value-backed accessor, taking a property reference instead of an initial value, e.g.:
class C {
somekeyword foo someseparator #foo.value;
}This would be sugar for:
class C {
get foo () {
return this.#foo.value;
}
set foo (value) {
this.#foo.value = value;
}
}So the syntax to be bikeshedded is:
- Keyword to prepend the definition. Ideas:
aliasdelegateforwardderivedbindCould be confused with thebindmethodproxy: Could be confused with theProxyconstructor
- Separator between the property name and the property chain being proxied. Ideas:
=: Could be confused with the assignment operator=>: Could be confused with the arrow function operator, but also indicates a bindingviafromthroughto
For concreteness, we’ll use alias/to in the rest of this document.
These property chains are basically chains of [ . LiteralPropertyName ] | ComputedPropertyName ].
this. at the start is implicit.
They are not References however: conceptually, the entire chain is re-evaluated on each access.
There are many cases where multiple properties need to be forwarded through another object.
For example, when using delegation/forwarding patterns, such as ElementInternals, multiple properties need to be exposed.
| Current syntax | Potential new syntax |
|---|---|
class MyElement extends HTMLElement {
#internals = this.attachInternals();
static formAssociated = true;
// Expose
get form () {
return this.#internals.form;
}
get labels () {
return this.#internals.labels;
}
get checkValidity () {
return this.#internals.checkValidity;
}
get reportValidity () {
return this.#internals.reportValidity;
}
get willValidate () {
return this.#internals.willValidate;
}
get validationMessage () {
return this.#internals.validationMessage;
}
// ...
} |
class MyElement extends HTMLElement {
#internals = this.attachInternals();
static formAssociated = true;
// Expose
alias form = #internals.form;
alias labels = #internals.labels;
alias checkValidity = #internals.checkValidity;
alias reportValidity = #internals.reportValidity;
alias willValidate = #internals.willValidate;
alias validationMessage = #internals.validationMessage;
// ...
} |
or when implementing a first-class protocol:
| Current syntax | Potential new syntax |
|---|---|
class C implements Iterable {
get forEach () {
return this[Iterable.forEach];
}
get map () {
return this[Iterable.map];
}
get flatMap () {
return this[Iterable.flatMap];
}
get filter () {
return this[Iterable.filter];
}
get reduce () {
return this[Iterable.reduce];
}
get toArray () {
return this[Iterable.toArray];
}
get some () {
return this[Iterable.some];
}
get every () {
return this[Iterable.every];
}
get find () {
return this[Iterable.find];
}
static get from () {
return this[Iterable.from];
}
// ...
} |
class C implements Iterable {
alias forEach = [Iterable.forEach];
alias map = [Iterable.map];
alias flatMap = [Iterable.flatMap];
alias filter = [Iterable.filter];
alias reduce = [Iterable.reduce];
alias toArray = [Iterable.toArray];
alias some = [Iterable.some];
alias every = [Iterable.every];
alias find = [Iterable.find];
alias from = [Iterable.from];
// ...
} |
While alias improves things quite a lot already, it’s still a little repetitive.
There is not much to do in the second case, but perhaps there could be a destructuring-inspired shortcut when the names are identical:
class C {
alias {
form,
labels,
checkValidity,
reportValidity,
willValidate,
validationMessage
} from #internals;
}While by default both a setter and a getter would be added, composable setters could be used to make it read-only, either with silent rejection (DOM style) or loud rejection (JS style):
class C {
#foo = 1;
@validate(v => false)
alias #foo to foo;
}Do note however that this is only needed if the property being proxied is read-only, the error would just propagate naturally:
class MyElement extends HTMLElement {
#internals = this.attachInternals();
// ElementInternals.prototype.form is read-only
// so setting form will throw
alias form = #internals.form;
}Here, setting myElement.form would produce an error anyway, since ElementInternals.prototype.form is read-only.
Therefore, there is no need to prevent writes explicitly unless we want to swallow them.
Another direction for this proposal might be to become a syntax extension of grouped and auto-accessors. Customization of the backing store was already requested in #14.
The syntax proposed in the issue was accessor(#x) x = 1;.
Syntax like accessor x : #x = 1 would likely clash with TS.