Skip to content
MotionActor
Esc
↑↓navigate↵open⌘Jpreview
On this page

Derived values

Read-only values computed from other actor reads, evaluated at resolution time.

class Timer extends LayoutActor {
  elapsed = signal(0);

  remaining = derived(() => Math.max(0, 30 - this.elapsed()));
  progress = derived(() => this.elapsed() / 30);
  label = derived(() => `00:${Math.ceil(this.remaining()).toString().padStart(2, "0")}`);
}

A derived has no setter. It runs at resolution time, reads whatever it reads, and returns a value for that frame.

One signal is animated. remaining reads elapsed, label reads remaining, and the whole chain is recomputed from scratch on every frame — including when you scrub backwards. Nothing is cached between frames, so there is no stale value to invalidate.

Use it for

  • Values that should never be written directly.
  • Presentation computed from state — geometry, transforms, formatted labels — so the view reads finished numbers instead of doing arithmetic.
  • Convenience reads built from signals, channels or other derived values.

Derived chains

Derived values compose, and composing them is how presentation stays out of the renderer:

public dockMetrics = derived((): DockMetrics => {
  const { width, height } = this.localLayout();
  return resolveDockMetrics(this.skin(), this.dock.items().length, width, height);
});

public dockIconCenters = derived(() => {
  const { iconSize, gap, paddingX, left, centerY } = this.dockMetrics();
  // …one screen-space center per dock item
});

public windowVisuals = derived((): WindowVisual[] => {
  const centers = this.dockIconCenters();
  return this.windows().map((window, index) => ({
    id: window.id,
    transform: buildWindowTransform(window, centers[window.appId] ?? null),
    opacity: clamp01(window.openProgress),
    zIndex: 100 + index,
  }));
});

The renderer then does nothing but windowVisuals().map(...). See the Desktop example for this pattern in full.

Authoring forms

Arrow form is the default. The function form with an explicit this type is equivalent — both resolve this to the live per-frame actor.

// default
width = derived(() => this.scale() * 320);

// explicit this
width = derived(function (this: Card) {
  return this.scale() * 320;
});

Purity

A derived body runs during frame resolution, which means it is bound by the determinism contract. No Date.now(), no Math.random(), no reading or writing module-level mutable state. A derived value that is not a pure function of its reads breaks seeking, and it breaks it quietly — forward playback looks correct and only scrubbing backwards disagrees.

Do not use it for

Instead of Use
Writable state signal
Host-fed runtime facts channel
Side effects on state change watch

Was this page helpful?