JM Family Design System

Motion

Motion is a teaching tool. It tells users what happened, where they are, and what they can do next — without adding a single word to the interface. Use these guidelines to make motion that informs, never to entertain.

Why motion matters

A static interface asks the user to do extra work. Did the menu actually open? Did the system register my click? Where did that panel go when I closed it? Motion answers those questions before the user has to ask them. It is not decoration — it is how the interface stays honest about its own state.

But motion is also the easiest thing to over-do. A page that springs, bounces, and shimmers feels alive in the design mock and exhausting in real use. The bar we hold motion to in this system is simple: unobtrusive, brief, and subtle. If you cannot explain in one sentence what an animation is teaching the user, take it out.

Motion has a budget
Treat motion like spacing tokens. Each animation costs the user a few hundred milliseconds of attention. Spend it where it pays the user back — feedback, state changes, navigation — and nowhere else. A page with eight things animating at once has spent the budget on noise.

The four purposes of motion

Every animation in our products should serve exactly one of these. If you cannot pick one, the animation is not earning its place.

Feedback

Motion that confirms the system recognized the user's action. Without it, users wonder whether the click registered. A button that briefly compresses on press, a toast that slides in after a save, a chevron that rotates on accordion open — each one closes the loop the user just opened.

motion.duration.fastmotion.easing.default

State change

Motion that communicates a transition between modes or values — especially when the change is hard to perceive at a glance. A numeric value counting up, an edit icon morphing into a save icon, a row marking itself complete. Same element, new state, the eye keeps track.

motion.duration.normalmotion.easing.default

Spatial navigation

Motion that helps users keep their bearings in an information hierarchy. A panel sliding in from the right, a modal scaling up from the trigger, a drawer pushing content aside — each shows the user where they came from and where they can return.

motion.duration.slowmotion.easing.enter

Signifier

Motion that hints at what the user can do — a list item gently pulsing to suggest it's swipeable, a card peeking over a fold to suggest scroll. Use sparingly; signifiers degrade fast when the interface uses them everywhere.

motion.duration.slowermotion.easing.spring

Principles of effective UI motion

Six rules to apply when designing motion. Hold every animation to them; the rule that would reject it usually wins.

01

Ease, don't lurch

Motion should slow into and out of its endpoints, not start and stop at constant speed. Linear motion feels mechanical because nothing in the physical world moves that way. Use motion.easing.default for general transitions; reach for motion.easing.enter and motion.easing.exit when an element is appearing or disappearing — they're tuned to feel right for each direction.

02

Stagger by importance

When multiple elements appear at once — list items, cards in a grid, fields in a form — animate them in a quick succession rather than all together. The eye follows the last element to settle, so a staggered sequence subtly directs attention. Keep the offset small: 40 to 80ms between siblings is usually right. More than ten elements? Stop staggering and fade the whole group.

03

Transform, don't replace

When an element changes state, morph it into its new form rather than instant-swap. A submit button that smoothly stretches into a progress bar and resolves to a checkmark teaches the user that all three states belong to the same action. Two separate elements popping in and out teach nothing.

04

Layer with intent

When elements stack — modals over content, popovers over panels — use opacity, blur, or scrim to make the relationship unambiguous. Background content should clearly recede; foreground should clearly own the user's attention. The animation that opens the overlay is also the animation that explains the hierarchy.

05

Connect cause to effect

The thing the user clicked should appear connected to the thing that responds. A panel that opens from the menu icon should visibly originate from that icon, not from a random screen edge. Child elements move with their parent, and origins are honored.

06

Earn every millisecond

Motion that takes longer than 500ms is no longer motion — it's a wait. If a transition feels slow, it probably is. The default should be motion.duration.normal (200ms); reach for slower tokens only when the transition covers a large spatial change or carries critical information.

Duration & easing tokens

All motion in our products uses these tokens. Never hardcode a duration in milliseconds or an easing curve in cubic-bezier(...). The tokens are tuned to feel coherent across the product, so two engineers building two features end up with motion that visually agrees.

Duration

TokenValueWhen to useDemo
motion.duration.instant0msReduced-motion fallback. Effectively disables the transition while preserving the property change.
motion.duration.fast100msHover states, focus rings, button press feedback, micro-interactions on a single element.
motion.duration.normal200msDefault for nearly everything. Tab switches, accordion expand/collapse, toast in/out, value changes.
motion.duration.slow300msPage-level transitions, modal open/close, drawer slide, larger spatial shifts.
motion.duration.slower500msComplex multi-step animations or signifier nudges. Use sparingly — anything longer is a wait, not motion.

Easing

Easing demos run at motion.duration.slower (500ms) so the shape of each curve is visible. In production, easing is usually paired with a faster duration.

TokenCurveWhen to useDemo
motion.easing.defaultGeneral-purpose. The right answer 80% of the time.
motion.easing.enterElements appearing — fast at the start, eases into rest. Feels like arrival.
motion.easing.exitElements leaving — eases out of rest, fast at the end. Feels like departure.
motion.easing.springPlayful overshoot. Use for FABs, success confirmations, signifier nudges — never for routine state changes.

Recommended pairings

SituationDurationEasing
Hover state on a button or linkmotion.duration.fastmotion.easing.default
Tab or accordion togglemotion.duration.normalmotion.easing.default
Toast or banner appearingmotion.duration.normalmotion.easing.enter
Toast or banner dismissingmotion.duration.normalmotion.easing.exit
Modal or dialog openingmotion.duration.slowmotion.easing.enter
Modal or dialog closingmotion.duration.slowmotion.easing.exit
Drawer or side panel sliding inmotion.duration.slowmotion.easing.enter
FAB expanding to action setmotion.duration.slowmotion.easing.spring
Numeric value counting upmotion.duration.slowmotion.easing.default
Loading skeleton shimmer (looped)motion.duration.slowermotion.easing.default

Accessibility

The most important rule on this page: always respect prefers-reduced-motion. A non-trivial slice of users — people with vestibular disorders, attention sensitivities, or simply low tolerance for movement on screen — has explicitly asked their device to hold the animation. The system must honor that. WCAG 2.3.3 (Animation from Interactions, Level AAA) is the baseline expectation, not an aspiration.

How we honor it

Wrap every animation in a media query that disables motion when the user has set prefers-reduced-motion: reduce. This pattern already lives in the base styles — component authors don’t need to repeat it, but they do need to ensure custom animations use transition or animation properties (which the query covers), not requestAnimationFrame loops or JS-driven motion (which it does not).

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0ms !important;
    scroll-behavior: auto !important;
  }
}

For JS-driven motion, check the user preference explicitly:

const prefersReducedMotion =
  typeof window !== 'undefined' &&
  window.matchMedia('(prefers-reduced-motion: reduce)').matches;

if (prefersReducedMotion) {
  // Skip the animation; jump to end state.
} else {
  // Run the animation.
}

What “reduced” means in practice

Reduced motion is not “no feedback.” A button still needs to confirm it was clicked; an error still needs to surface. The right substitution depends on the purpose:

  • Feedback: keep the state change, drop the easing. Replace the slide with an instant color change.
  • State change: swap the morph for an immediate swap. The information matters more than the transition.
  • Spatial navigation: replace slide-and-fade with a cross-fade, or skip motion and rely on layout / focus to orient the user.
  • Signifier: drop the signifier entirely. If the affordance only exists via motion, build a static affordance too.
Vestibular disorders
Large-area motion is the highest-risk pattern for users with vestibular disorders. Parallax backgrounds, full-screen slides, and zoom transitions that fill the viewport can trigger dizziness, nausea, or migraines. Even in non-reduced-motion mode, prefer small, contained motion to full-bleed effects.

Do and don’t

Six paired examples grounded in real situations. Each pair shows what to ship and what to leave on the cutting-room floor.

Hover feedback
DoUse the fast duration with default easing.transition: background-color var(--motion-duration-fast) var(--motion-easing-default);
Don’tHardcode a slow ease-in-out — it bypasses tokens and feels sluggish.transition: background-color 0.5s ease-in-out;
List entrance
DoStagger about 5 list items 60ms apart with normal duration and enter easing. The cascade directs attention to the last-settled item.
Don’tStagger 30 list items the same way. The cascade becomes a distraction — cross-fade the whole group instead.
Modal open
DoScale from 96% to 100% and fade in over slow duration with enter easing. Contained motion, clear origin.
Don’tSlide the modal in from outside the viewport with slower duration. Large-area motion for an everyday action sets off vestibular-sensitive users.
Form save confirmation
DoMorph the Save button into a progress indicator, then into a checkmark. Same element, three states — easy to follow.
Don’tHide the button and show a toast that auto-dismisses. The user may miss it — see the toast-for-critical-errors anti-pattern.
Decorative shimmer
DoUse a skeleton shimmer (slower duration, looped) only while content is loading. Stop it the moment content arrives.
Don’tKeep the shimmer running after content loads. It signals "still loading" indefinitely and erodes trust.
Respecting the user
DoRely on the global prefers-reduced-motion rule and test the page in reduced-motion mode before shipping.
Don’tDrive animations via requestAnimationFrame without checking the media query. Those animations sail past the global rule.

Further reading

  • Nielsen Norman Group, Animation for UX: Purpose, Personality, and Performance — useful background on why UI motion is a feedback tool, not decoration.
  • Uxcel, 12 Principles of Animation: A Guide to Motion Design — broader survey of animation principles for designers who want a deeper grounding.
  • WCAG 2.3.3, Animation from Interactions — the conformance criterion behind the accessibility section.
  • WCAG 2.3.1 and 2.3.2, Three Flashes — flash-rate rules that apply when any element flashes more than three times per second.

The principles in this page stand on their own — they are written for our context and are not numbered against any external source.