Skip to content
GetUrUI

Motion guide

Motion is the product, so the rules are strict: springs for things that move, short fades for things that change, nothing that blocks reading.

Springs

UseMotion config
Indicators, active pills, layoutId{ type: "spring", stiffness: 420, damping: 32 }
PresseswhileTap={{ scale: 0.96 }}, stiffness 520, damping 26
Panels, sheets, accordions{ type: "spring", stiffness: 260, damping: 28 }
Toasts, popovers{ type: "spring", stiffness: 380, damping: 30 }
Digit reels{ type: "spring", stiffness: 160, damping: 20 }

Durations and easings

For plain CSS transitions the theme ships spring curves as linear() easings:

geturui.css
1--ease-out: cubic-bezier(.22, 1, .36, 1);          /* fades, blur, colour */
2--ease-spring: linear(0, .009, .035 2.1%, …, 1);    /* ~16% overshoot */
3--ease-spring-soft: linear(0, .06, .22 8%, …, 1);  /* ~4% overshoot */

Durations: 140ms hover colour, 220ms small moves, 420ms panels, 560ms springs. Use them as Tailwind classes: transition-transform duration-500 ease-spring.

Patterns

  • Press: scale to 0.96 in 90ms, spring back.
  • Hover lift: cards rise 3px; arrows nudge 3px.
  • Swap: content changes crossfade with an 8px blur rather than sliding.
  • Reveal: one word-by-word reveal per page, on the H1. Don't animate every section on scroll.
  • Indicator: a single element that glides between options (layoutId), never one per option fading in and out.
swap.tsx
1<AnimatePresence mode="popLayout" initial={false}>
2  <motion.span
3    key={label}
4    initial={{ opacity: 0, y: 6, filter: "blur(8px)" }}
5    animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
6    exit={{ opacity: 0, y: -6, filter: "blur(8px)" }}
7  >
8    {label}
9  </motion.span>
10</AnimatePresence>

Performance

Animate transform, opacity and filter. Height changes go through Motion's layout or height: "auto", never per-frame state. Don't animate box-shadow on scroll.

Reduced motion

Every item respects prefers-reduced-motion: large movements become fades and loops stop. In your own code, use Motion's hook:

example.tsx
1const reduce = useReducedMotion();
2<motion.div animate={{ x: reduce ? 0 : 120, opacity: 1 }} />