Motion

Decorator

A decorator dims or tints the previous screen during a transition. It uses that transition's timing and follows a swipe back without any code.

2 min read

Places

Saved for later

Idlecupertino
Duration700ms
LiveOpen a place and drag from the left edge: the dim over the list follows your finger and keeps fading after you let go.

What a decorator is

A decorator is a layer flemo puts over the INACTIVE side of a screen transition: the previous screen, moving behind on a push or coming back on a pop. It is not an element you render. A transition sets it with options.decoratorName, and the Router that runs that transition registers it.

Use one for depth that belongs to the navigation itself, such as a dim behind a sheet. A shadow or overlay that belongs to one screen's content is a or ordinary markup instead.

The built-in overlay

cupertino sets overlay, a 10% black dim (rgba(0, 0, 0, 0.1)) that only animates opacity. material, layout and none set no decorator.

  • It sets no durations, so it runs in step with whichever transition uses it, at any length
  • It leaves its easing unwritten: a decelerating curve made for movement would do most of the darkening at once, so the default ease spreads the dim evenly
  • Holding backgroundColor fixed and animating only opacity keeps it on the compositor on every browser

Writing a decorator

SlotApplies to
initialThe starting style on a newly mounted screen
idleThe active screen, including the top screen closing on pop. Normally invisible
enterThe screen moving into, or staying in, the background
exitThe screen coming back on pop. Animates from enter; match it to idle
transitions/dive.ts
import { createDecorator, createTransition } from "@flemo/react";​const DIM = "rgba(0, 0, 0, 0.4)";​// No durations: every variant uses the timing of the transition that sets it.export const dim = createDecorator({  name: "dim",  initial: { opacity: 0, backgroundColor: DIM },  idle: { value: { opacity: 0, backgroundColor: DIM } },  enter: { value: { opacity: 1, backgroundColor: DIM } },  exit: { value: { opacity: 0, backgroundColor: DIM } }});​export const dive = createTransition({  name: "dive",  initial: { y: "100%" },  idle: { value: { y: 0 }, options: { duration: 0 } },  enter: { value: { y: 0 }, options: { duration: 0.4, ease: "ease-out" } },  exit: { value: { scale: 0.94 }, options: { duration: 0.4, ease: "ease-out" } },  enterBack: { value: { y: "100%" }, options: { duration: 0.3, ease: "ease-in" } },  exitBack: { value: { scale: 1 }, options: { duration: 0.3, ease: "ease-in" } },  options: { decoratorName: "dim" }});​declare module "@flemo/react" {  interface RegisterDecorator {    dim: "dim";  }  interface RegisterTransition {    dive: "dive";  }}
tsx
<Router transitions={[dive]} decorators={[dim]}>  <Route path="/" element={<Home />} /></Router>

Leave duration out. A decorator inherits duration and delay from the transition that sets it, variant by variant. ease never inherits: a curve drawn for movement is wrong for a fade.

Timing

  • Each decorator variant takes its timing from the screen variant that runs at the same moment. A decorator's enter runs with the screen's exit, and its exit with the screen's exitBack
  • Asymmetric transitions pass their asymmetry on. Under material, a dim runs 0.35s on push and 0.25s on pop
  • An explicit duration wins, including 0 to change at once. Resolution uses ??, so an authored 0 survives
  • A decorator has no dismiss. It covers the previous screen of one transition, and the closing screen is not that screen

A literal duration longer than the screen's leaves a grey cast still fading on a screen that has already stopped. Inheriting avoids it.

Swipe

A decorator that declares only variant values follows a swipe back by itself. During the drag it shows the style the transition would give it at the same point of the screen's movement, read through the screen's easing and then its own. When the finger lifts, it plays the rest of the same motion: to the end if the swipe goes back, or back to where it started if the swipe is cancelled.

Writing onSwipe or onSwipeStart makes the decorator handle its own drag and turns that default off. Add hooks only for a gesture shape the declared variants cannot express, never just to make the dim track the finger.

Hooks and raw decoratorsDetails
  • Decorator hooks receive (triggered, { animate, currentDecorator, prevDecorator }). onSwipe also receives progress from 0 to 100
  • onSwipeEnd alone does not turn the default off; onSwipe and onSwipeStart do
  • createRawDecorator takes the ten raw slots of createRawTransition, with the same inheritance, for push, replace and pop styles that differ
Edit on GitHub