RamosLabs DS

Motion With a Job to Do

Every animation here earns its place by answering one question for the person using the product: where did that come from, are these the same thing, did my tap register. No job, no motion. The system is deliberately small, three durations and five easings, so the whole product moves at one tempo: fast, and quiet.

Purposeful, Not Decorative

Good motion orients. It carries the eye from a tap to the panel that opened, shows a row slid instead of teleported, confirms a request landed. Decoration does none of that and costs the same frame time. Motion earns its place by clarifying a change, never by dressing one up.

Recommended
  • Orientation. A menu grows from the button that opened it.
  • Continuity. A row animates to its new spot instead of jumping.
  • Feedback. A control settles after a tap, confirming the press.
Avoid
  • Entrances that animate with nothing to orient.
  • Looping, attention-grabbing movement with no status behind it.
  • Long, cinematic reveals that make the interface wait.

The Three Durations

Match the duration to the size of the change. A small, local change should feel instant; a surface taking over the screen earns a beat. Nothing runs past 300ms, where responsive starts to feel slow. Hover a card to feel each one.

150ms
Fast
--duration-fast

Local state on a control the user is already watching: a hover tint, a focus ring, an icon toggling, a checkbox filling.

200ms
Normal
--duration-normal

The default. Things entering, leaving, or moving a short way: a dropdown, a tooltip, a tab indicator, an accordion.

300ms
Slow
--duration-slow

A large surface that takes over the view and needs a beat to read: a modal, a bottom sheet, a full-screen overlay.

Larger surface or longer travel, longer duration. The ceiling is 300ms, and most motion lives at 200ms.

Five Easings, Five Jobs

Easing is the shape of speed over time, and it is where motion says something. Fast then settling reads as arriving; slow then accelerating reads as leaving. Pick the wrong one and the interface feels laggy or mechanical. Each curve below has one job.

Linear
cubic-bezier(0, 0, 1, 1)

--easing-linear Constant speed, no start or finish to shape. Only for continuous loops: spinners, indeterminate progress. On anything that begins and ends it feels robotic.

Ease out
cubic-bezier(0, 0, 0.2, 1)

--easing-out Quick start, gentle landing. The default for anything entering or answering a tap. It moves at once, which reads as responsive, then decelerates into place.

Ease in
cubic-bezier(0.4, 0, 1, 1)

--easing-in Slow start, accelerating exit. For elements leaving the screen, they gather speed and get out of the way. Never for entrances, a slow start there reads as lag.

Ease in out
cubic-bezier(0.4, 0, 0.2, 1)

--easing-in-out Eased on both ends. For an element moving between two on-screen positions, both visible the whole time: a reordering row, a toggle thumb, a tab indicator.

Spring
cubic-bezier(0.34, 1.56, 0.64, 1)

--easing-spring Overshoots, then settles. Reserved for a small, discrete success you want to feel alive: a checkmark, a like, a badge popping in. Comfort not information, so gate it behind reduced motion and keep it off large surfaces.

Anatomy of a Transition

Every transition names three things: the property, a duration token, an easing token. Name the property, never transition: all, which animates things you never meant to touch. Reference the tokens instead of hardcoding milliseconds, so the whole product keeps one tempo.

transition:opacityvar(--duration-normal)var(--easing-out);
property
What changes. Prefer transform and opacity. Name it, never all.
duration
How long. 150ms local, 200ms to enter or leave, 300ms for a takeover.
easing
The feel. Out to enter, in to leave, in-out to move, linear to loop, spring to celebrate.

Performance Is Part of the Contract

A frame at 60fps is about 16ms. The browser can animate two properties, transform and opacity, on the compositor, off the main thread, with no layout and no repaint. Animating geometry recomputes layout every frame, and that layout thrash is the usual cause of jank. This is how the rendering pipeline works, not a preference, so the system animates transform and opacity by default.

Recommended
transformopacity

Handled on the compositor, off the main thread. No layout, no paint. A slide is translateY, not a change to top.

Avoid
widthheighttopleftmargin

Each frame recomputes layout, then repaints. On a long list or a weak device this drops frames. Express the same motion with transform.

Reduced Motion Is the Floor

Some people feel physically ill from on-screen movement. The browser exposes their OS setting as prefers-reduced-motion: reduce, a firm request to hold still that every animation honors. It aligns with WCAG 2.1: 2.3.3 asks that interaction-triggered motion can be disabled, and 2.2.2 covers looping motion.

Honoring it does not mean deleting meaning. Because information rides on color, opacity, and the final state, the interface stays legible without movement: collapse the animation, keep the destination. And keep a spinner turning slowly rather than freezing it, a stopped spinner reads as a hung app.

CSS
/* Default: motion is welcome. Enter fast, settle gently. */
.menu {
    transition: opacity var(--duration-normal) var(--easing-out),
                transform var(--duration-normal) var(--easing-out);
}

/* Firm request to hold still: collapse the movement, keep the meaning. */
@media (prefers-reduced-motion: reduce) {
    .menu {
        transition-duration: 1ms;       /* appears, does not fly in */
        transform: none;              /* no slide */
    }
    .spinner {
        animation-duration: 1.4s;    /* slower, not gone */
    }
}

Rules of Motion

Each rule stands on its own: what to do, then why it holds. Read the why once and the rule stops feeling arbitrary.

Rule

Give every animation a job: orientation, continuity, or feedback.

Why

Decorative motion clarifies nothing and costs the same frame time, so it is the first thing to cut.

Rule

Animate transform and opacity.

Why

The compositor carries them off the main thread with no layout or paint. Animating width, height, top, left, or margin recomputes layout every frame and drops frames.

Rule

Name the property, a duration token, and an easing token explicitly.

Why

transition: all animates properties you never meant to touch, and hardcoded milliseconds drift from the shared tempo.

Rule

Match the duration to the change: fast for local state, normal to enter or leave, slow for a takeover.

Why

Anything past 300ms reads as sluggish, so the ceiling holds and most motion sits at 200ms.

Rule

Pick easing by job: out to enter, in to leave, in-out to move, linear to loop.

Why

linear on anything with a start and an end feels robotic, and the wrong curve reads as lag or machinery.

Rule

Reserve spring for a small, discrete success, and gate it behind reduced motion.

Why

An overshooting modal looks broken, and spring is comfort, not information.

Rule

Honor prefers-reduced-motion: collapse the movement, keep the meaning.

Why

Motion alone must never carry meaning. Information rides on color, opacity, and the final state, so the interface stays legible without movement.

Token Reference

TokenValueRole
--duration-fast150msLocal state on one control: hover, focus, toggle
--duration-normal200msDefault. Enter, leave, or move a short distance
--duration-slow300msA large surface taking over the view. The ceiling
--easing-linearcubic-bezier(0, 0, 1, 1)Continuous loops only: spinners, indeterminate progress
--easing-incubic-bezier(0.4, 0, 1, 1)Elements leaving the screen
--easing-outcubic-bezier(0, 0, 0.2, 1)Elements entering, and taps. The default
--easing-in-outcubic-bezier(0.4, 0, 0.2, 1)Moving between two visible positions
--easing-springcubic-bezier(0.34, 1.56, 0.64, 1)Small discrete successes. Overshoots, then settles

Sources

  • Material 3, Motion. Duration buckets and easing sets; motion orients the user, maintains continuity, and gives timely feedback. m3.material.io/styles/motion
  • Apple Human Interface Guidelines, Motion. Motion should be purposeful and communicate status; gratuitous animation is discouraged and Reduce Motion must be respected. developer.apple.com/.../motion
  • web.dev and MDN, animation performance. Animate transform and opacity to stay on the compositor without layout or paint; use will-change sparingly because each promoted layer costs memory. web.dev/articles/animations-guide
  • MDN, prefers-reduced-motion. A device-level preference to minimize non-essential motion, mapping to the OS accessibility setting. developer.mozilla.org/.../prefers-reduced-motion
  • W3C WCAG 2.1. 2.3.3 Animation from Interactions (AAA) and 2.2.2 Pause, Stop, Hide (A) target vestibular symptoms such as dizziness and nausea. w3.org/WAI/WCAG21/.../animation-from-interactions
  • The three durations (150ms, 200ms, 300ms), the five named easings, the 300ms ceiling, and gating spring behind reduced motion are specific to this system's tokens. SPA House decisions, consistent with but not dictated by the cited sources.