Motion in Rafters

Motion communicates. It tells the user what happened, where they are, and what to expect next. Every animation in the system exists because it answers one of these questions. If it doesn’t answer any of them, it doesn’t move.

Principles

Respond, don’t perform. The interface reacts to the user. It never performs for an audience. A button press gets instant acknowledgment. A modal entrance gets smooth deceleration. A loading spinner gets linear rotation. Nothing bounces, slides, or fades without a reason the user would understand if asked.

Precision over expression. Durations are short. Curves are tuned. The fastest interactions (hover, focus) are nearly instant. The slowest (full-screen transitions) stay under 500ms. Nothing in the system exceeds 700ms. If an animation feels long, it is wrong.

Exits are faster than entrances. When something appears, the user needs a moment to comprehend it. When something disappears, the user already decided it should go. Entrance duration minus 50 to 100ms is the exit duration. This asymmetry respects where the user’s attention is.

Feedback survives reduce-motion. When a user enables reduced motion, we remove spectacle but preserve feedback. Spatial transitions become cross-fades. Bounces become critically damped. Hover states, focus rings, and press acknowledgments remain, because removing them creates uncertainty about whether input was received.

Nothing blocks interaction. No animation prevents the user from acting. If a modal is still opening and the user taps elsewhere, the modal closes immediately. Animations are interruptible by design.

Duration Scale

Our duration scale derives from the same mathematical progression as our spacing and typography. Each tier serves a specific interaction category.

TokenValuePurpose
duration-instant0msCursor changes, text selection, badge counts. No perceptible transition.
duration-micro100msFocus rings, press feedback. Must feel immediate but visible.
duration-fast150msHover states. The user’s cursor is already there, the response must match their speed.
duration-moderate250msDropdowns, tab switches, small reveals. Enough time to track the change without waiting for it.
duration-normal350msModal entrances, toggle animations, standard state transitions. The workhorse.
duration-slow500msSheet presentations, page transitions, large spatial movements. Reserved for transitions where the user needs orientation.

The ceiling is 500ms for standard UI. Full-screen transitions may reach 700ms but this is the absolute maximum. Research found that users perceive anything over 400ms as “waiting.” We allow up to 500ms only when the spatial movement is large enough to justify it.

Easing Curves

We define six curves. Each has a specific physical intention.

Standard

cubic-bezier(0.25, 0.1, 0.25, 1.0)

The default. Elements decelerate into their final position. This curve communicates precision: things arrive exactly where they should, with the confidence of something engineered rather than thrown.

Enter

cubic-bezier(0.0, 0.0, 0.2, 1.0)

Aggressive deceleration. Elements appear to materialize with purpose. Fast start, gradual settle. The initial velocity is high, which makes the entrance feel responsive even at longer durations.

Exit

cubic-bezier(0.4, 0.0, 1.0, 1.0)

Accelerating departure. Elements leave quickly without lingering. The curve front-loads the deceleration and ends with acceleration, which makes exits feel decisive.

Linear

linear

No easing. Constant velocity from start to finish. This is the only curve that feels mechanical, which is exactly right for progress indicators and loading states. The system is working, not interacting.

Spring Smooth

cubic-bezier(0.2, 0.9, 0.3, 1.0)

A CSS approximation of a critically-damped spring. The initial movement is fast (the spring is released), then the element settles smoothly into position. This curve feels natural, like something physical coming to rest.

Spring Snappy

cubic-bezier(0.2, 0.8, 0.2, 1.0)

A tighter spring with less settle time. Snappier than smooth, more organic than standard. This is the curve for interactions where responsiveness matters more than smoothness.

Semantic Motion Tokens

Components don’t reference durations and curves directly. They reference semantic motion tokens that encode the complete transition specification.

TokenDurationEasingProperty
motion-hoverduration-faststandardcolors
motion-focusduration-microlinearring, shadow
motion-pressduration-microspring-snappytransform, colors
motion-toggleduration-moderatespring-snappycolors, transform
motion-dropdown-induration-moderateenteropacity, transform
motion-dropdown-outduration-fastexitopacity, transform
motion-modal-induration-normalenteropacity, transform
motion-modal-outduration-moderateexitopacity, transform
motion-sheet-induration-slowspring-smoothtransform
motion-sheet-outduration-normalexittransform
motion-expandduration-normalenterheight, opacity
motion-collapseduration-moderateexitheight, opacity
motion-pageduration-slowspring-smoothopacity, transform

Notice the asymmetry: every -in and -out pair has a shorter exit. modal-in is 350ms, modal-out is 250ms. dropdown-in is 250ms, dropdown-out is 150ms. The user chose to leave.

What Gets No Motion

Some changes are instant. Adding motion would slow them down without communicating anything.

  • Cursor changes
  • Text color on validation (the border and ring animate, the text doesn’t)
  • Icon swaps (hamburger to X, chevron rotation)
  • Badge count updates
  • Scroll position changes
  • Breadcrumb updates
  • Tooltip appearance (opacity only, no spatial motion)

Reduced Motion

When the user enables prefers-reduced-motion: reduce, the system adapts.

Preserved (with reduced parameters):

  • Hover state changes (color transitions remain, duration unchanged)
  • Focus ring appearance (instant or near-instant, always was)
  • Press feedback (opacity and color, no transform)
  • Toggle state changes (cross-fade, no slide)

Replaced with cross-fade:

  • Modal entrance and exit becomes 150ms opacity fade
  • Sheet presentation becomes 250ms opacity fade
  • Page transitions become 200ms cross-fade
  • Dropdown open and close becomes 100ms opacity fade

Removed entirely:

  • All transform animations (scale, translate, rotate)
  • All bounce and overshoot
  • Loading spinner rotation (replaced with pulsing opacity)
  • Parallax effects
  • Background ambient motion

The principle: the user opted out of spatial movement, not out of knowing what changed. We preserve the information, remove the spectacle.

The Ceiling Rule

If you are writing an animation that takes longer than 500ms, stop. Either the element is moving too far (break the transition into smaller steps), the element is too large (cross-fade instead of spatial motion), or the animation is decorative (remove it).

The only exception: full-screen page transitions in applications with spatial navigation models, where 700ms is the absolute ceiling.

If a designer needs to exceed these limits, they use the why-gate. The override is recorded with a reason, the previous value is preserved, and the system remembers that this was a conscious decision, not a default.