Skip to main content
motion

The Timeline Editor Taught Me How to Design State

Timeline editors like After Effects and Lottie have explicit state machines. Designing one taught me more about UI state than any React tutorial.


5 min read

I spent a few months working closely with After Effects exports and Lottie animation editors, and the most useful thing it taught me had nothing to do with keyframes. It was state machine design. Timeline tools make state explicit and visible in a way that UI component code usually doesn't — and once you've seen it that clearly, you can't unsee it in every component you build.


The Timeline's State Machine

An animation timeline — whether you're looking at After Effects, the Lottie editor at lottiefiles.com, or the CSS animation panel in Chrome DevTools — runs an explicit state machine:

idle → playing → paused
  ↑       ↓        ↓
  └── scrubbing ───┘
         ↓
      rendering

Each state has different controls available. In playing, the scrubber moves automatically. In paused, you can drag the scrubber manually. In scrubbing, time is controlled by user input and playback is suspended. In rendering, everything is locked.

These aren't just UI states. They determine what events are legal. You can't scrub while rendering. You can't play while scrubbing. The state machine makes illegal transitions impossible by disabling the controls that would trigger them. That's good state machine design.

What This Transfers to Component Design

Most React components treat state as a bag of booleans: isLoading, isOpen, hasError, isSubmitting. The problem with booleans is that they allow impossible combinations. isLoading: true, hasError: true, isSubmitting: true — what does that even mean? Which one wins?

A timeline editor wouldn't allow these combinations because it doesn't store state as booleans. It has a single mode that can only be one value at a time:

type FormState =
  | { mode: "idle" }
  | { mode: "submitting" }
  | { mode: "success"; data: SubmissionResult }
  | { mode: "error"; error: string; retryable: boolean };

This is what XState, Zag.js, and the useReducer pattern are pointing at. One source of truth for mode. The component renders based on that mode, not by interrogating four separate booleans and hoping they're consistent.

The Bezier Editor Parallel

The most instructive part of working with After Effects is the Bezier curve editor for easing. You drag the control handles, and the animation updates in real time. The tool is making two things visible simultaneously: the mathematical curve (an abstract representation) and its physical effect (the actual motion).

This parallel — abstract representation plus live consequence — is exactly what good component design tools show. Storybook with controls is this. The React DevTools state inspector is this. You see the state (the control handles) and the rendered output (the motion) at the same time.

When I design a complex UI component now, I think in these terms: what's the abstract state (the control handles), and what's the visible consequence of each state value? If I can't describe both clearly, the state model is probably wrong.

Scrubbing as a Design Pattern

Scrubbing — dragging through time to preview frames — is a specific interaction pattern that appears everywhere once you start looking: video players, audio editors, 3D timeline scrubbing in Spline, even range inputs in forms.

What makes scrubbing interesting is its state requirements. While scrubbing, the system is in a "user controls time" mode. When scrubbing ends, the system returns to wherever it was (playing or paused). The scrubbing mode is transient — it interrupts and then restores.

This pattern shows up in UI components as well. A drag-to-reorder list has a dragging state that interrupts normal scroll behavior and restores it on drop. A color picker in "hue drag" mode suspends other interactions. Modeling these as explicit transient states rather than boolean flags makes the component's behavior predictable and testable.

Making State Transitions Visible

After Effects has an undo history panel. Every action — move keyframe, change easing, add layer — is listed. You can click any point in history and return to it. The state machine's full transition history is visible and reversible.

Most UI components have no history. But the mental model is useful: design your state transitions as if they need to appear in an undo history. Give each transition a name: "user submitted form," "API returned error," "user dismissed error and retried." If you can name the transition, you can test it. If you can test it, you can handle it.

The timeline editor made state visible because timelines are inherently about time and change. Building components teaches the same lesson, just more slowly and with less immediate feedback. The tool with the explicit state machine trains the intuition faster.