---
title: reactiveSignal
description: One nested value, written leaf by leaf or as a whole.
---

A `reactiveSignal` holds one nested value, such as a playback model or a list of markers.
Every leaf is a signal of its own with `set`, `tween` and `transition`, so separate parts of
the value can move on separate schedules, and the actor still has one field to read.

```ts twoslash title="timeline.ts"
import { Actor, reactiveSignal } from "@motionactor/core";

export class Timeline extends Actor {
  playback = reactiveSignal({
    time: 0,
    selection: { start: 0, end: 0 },
    markers: [{ at: 1, label: "Beat" }],
  });
}
```

```ts twoslash
// @filename: timeline.ts
import { Actor, reactiveSignal } from "@motionactor/core";
export class Timeline extends Actor {
  playback = reactiveSignal({
    time: 0,
    selection: { start: 0, end: 0 },
    markers: [{ at: 1, label: "Beat" }],
  });
}
// @filename: timeline.scene.ts
// ---cut---
import { createScene } from "@motionactor/core";
import { Timeline } from "./timeline";

const compiled = createScene(function* (ctx) {
  const timeline = ctx.spawn(Timeline);
  const { playback } = timeline;

  playback.markers.push({ at: 2, label: "Drop" });
  yield* ctx.all(
    playback.time.tween(4, 1),
    playback.selection.end.tween(2, 1),
  );
  return { timeline };
}).build({ viewport: { width: 1920, height: 1080 }, fps: 30 });

compiled.at(15).scene.timeline?.playback;
// { time: 2, selection: { start: 0, end: 1 },
//   markers: [{ at: 1, label: "Beat" },
//             { at: 2, label: "Drop" }] }
```

## The value

The initial value fixes the shape. It may hold plain objects, arrays, numbers, strings,
booleans and `null`. Anything else, such as a `Date`, a `Map` or a class instance, throws
when it is declared or written.

A leaf is a number, string, boolean or `null`, and an array that holds only numbers counts as
one leaf, so `[x, y]` tweens as a point. Leaves are called to read: `playback.time()`. An
object or array node is not callable; read it with `peek()`, or read its leaves.

## Writing

| Node | Methods |
| --- | --- |
| Leaf | `set`, `tween`, `transition`, as on a [signal](/docs/reactivity/signal) |
| Object | `set`, `transition` |
| Array | `set`, `transition`, `push`, `replaceAt`, `removeAt`, and `[i]` for an element |

`playback.markers[0].label.set("Intro")` writes one leaf of one element.

A node's `transition` takes a function of a draft, the eased progress and the start value.
Change the draft in place, or return a new value:

```ts twoslash
// @filename: timeline.ts
import { Actor, reactiveSignal } from "@motionactor/core";
export class Timeline extends Actor {
  playback = reactiveSignal({
    time: 0,
    selection: { start: 0, end: 0 },
    markers: [{ at: 1, label: "Beat" }],
  });
}
// @filename: timeline.scene.ts
// ---cut---
import { createScene } from "@motionactor/core";
import { Timeline } from "./timeline";

const compiled = createScene(function* (ctx) {
  const timeline = ctx.spawn(Timeline);
  yield* timeline.playback.transition((draft, progress) => {
    draft.time = 4 * progress;
    draft.selection.end = 4 * progress;
  }, 1);
  return { timeline };
}).build({ viewport: { width: 1920, height: 1080 }, fps: 30 });

compiled.at(15).scene.timeline?.playback.selection; // { start: 0, end: 2 }
```

Use a node's `transition` when several leaves change as one step. It is one write, so the
leaves cannot drift apart.

## Writes in the same frame

Writes that start on the same frame compose when their paths are disjoint, as `time` and
`selection.end` do above. Two animated writes whose paths overlap have no single answer: the
same leaf twice, or a node and a leaf inside it, like `selection` and `selection.end`. The
second write throws while the scene compiles, with an error that names both paths:

```text
Conflicting reactive signal writes for Timeline-1:playback at frame 0:
time conflicts with time
```

A write that starts on a later frame takes over the path from that frame.

## When to use one

A flat value is a [`signal`](/docs/reactivity/signal). A list that only ever changes as a whole
is also a signal, written with `transition`. Reach for `reactiveSignal` when leaves of one
value animate separately. A set of unrelated values under one name is a
[`group`](/docs/reactivity/group) of signals, which each stay a spawn input.

A `reactiveSignal` field takes a plain value as a spawn input. A `derived` input throws.
