Animations

Motion patterns for My Trainer Connect. All animations use motion/react (Framer Motion) with spring physics. CSS keyframe animations and Tailwind animate utilities are never used for custom animations in the app.

Animation Principles

motion/react Only

Always use motion from motion/react. Never use CSS @keyframes, Tailwind animate utilities, or raw CSS transitions for custom animations.

Spring Physics

Springs create natural, organic motion. Configure stiffness and damping instead of duration and easing curves. Springs never feel artificial.

Viewport-Triggered

Entrance animations trigger when elements scroll into view using whileInView. Elements animate once with viewport: { once: true }.

Stagger for Sequences

Groups of elements animate in sequence using staggerChildren: 0.08. Creates a cascading reveal effect that guides the eye through content.

Spring Configurations

AnimatedElement

Page content, cards, sections

type: 'spring'
stiffness: 50
damping: 20

MagneticButton

Hover interactions, click feedback

type: 'spring'
stiffness: 150
damping: 20

CountingNumber

Animated stats, number reveals

stiffness: 50 / duration
damping: 30 + duration * 5
Duration-responsive spring
48

Entrance Animation

Standard Entrance

The default entrance animation for all page content. Combines fade, vertical slide, and blur for a smooth reveal.

Initial State
opacity: 0
y: 24px
filter: blur(4px)
Animate To
opacity: 1
y: 0
filter: blur(0px)
Code
<motion.div
  initial={{ opacity: 0, y: 24, filter: 'blur(4px)' }}
  whileInView={{ opacity: 1, y: 0, filter: 'blur(0px)' }}
  viewport={{ once: true }}
  transition={{ type: 'spring', stiffness: 50, damping: 20 }}
/>
Live Demo
P
Program Card
Animates into view

Stagger Pattern

Sequential Reveal

When multiple elements need to appear together, stagger their entrance to create a cascading reveal. Uses the parent container's staggerChildren property.

Container
staggerChildren: 0.08
Code
<motion.div
  initial="hidden"
  whileInView="visible"
  viewport={{ once: true }}
  variants={{
    visible: {
      transition: { staggerChildren: 0.08 }
    }
  }}
>
  {items.map(item => (
    <motion.div
      key={item.id}
      variants={{
        hidden: { opacity: 0, y: 24, filter: 'blur(4px)' },
        visible: { opacity: 1, y: 0, filter: 'blur(0px)' }
      }}
    />
  ))}
</motion.div>
Live Demo
1
Warm Up
5 min cardio
2
Bench Press
4 x 8 reps
3
Squats
4 x 10 reps
4
Deadlift
3 x 6 reps
5
Cool Down
Stretching

Hero Animation Sequence

Sequential Hero Elements

Hero sections animate each element with increasing delays. This creates a reading-order reveal: badge first, then title, subtitle, and CTA buttons.

Delay Sequence
Element 1 (badge): 0.1s
Element 2 (title): 0.2s
Element 3 (subtitle): 0.3s
Element 4 (CTA): 0.4s
Code
<AnimatedElement delay={0.1}>
  <Badge>New</Badge>
</AnimatedElement>
<AnimatedElement delay={0.2}>
  <h1>Title</h1>
</AnimatedElement>
<AnimatedElement delay={0.3}>
  <p>Subtitle</p>
</AnimatedElement>
<AnimatedElement delay={0.4}>
  <ButtonGroup />
</AnimatedElement>
Live Demo
Now Available

Find Your Perfect Trainer

Connect with certified personal trainers matched to your goals, preferences, and training style.

Component Transitions

Dialog

Zoom + fade from center. Scale 0.95 to 1, opacity 0 to 1.

scale: 0.95 → 1   opacity: 0 → 1
Confirm Action
This will save your changes.

Dropdown

Slide down + fade. Translates from y: -4px to 0, with opacity.

y: -4px → 0   opacity: 0 → 1

Sheet

Slides in from the right edge. Uses spring easing for a natural stop.

x: 100% → 0   easing: spring
Filters

Toast

Slides up from the bottom with a subtle scale. Appears from off-screen.

y: 16px → 0   scale: 0.95 → 1
Program saved
Your changes have been saved.

Guidelines

Context Animation Details
Page loads Recommended Viewport-triggered entrance animations. Use whileInView with once: true. Stagger child elements at 0.08s.
Route changes Minimal Avoid route transition animations. They cause visual jank with SSR hydration. Let content appear instantly.
User interactions Recommended Springs for hover/click feedback. Use MagneticButton spring (stiffness: 150, damping: 20) for snappy response.
Data updates None Optimistic updates should be instant. No animation on cache updates or mutation responses. Immediate UI feedback.
Overlays (dialog, sheet) Recommended Short, eased transitions. Dialog: zoom + fade (~200ms). Sheet: slide from edge (~300ms spring). Toast: slide up (~300ms spring).
Loading states Minimal Use CSS animate-pulse for skeletons only (this is the one exception to the motion-only rule). Spinner icons use animate-spin.
Do
  • Use motion from motion/react for all custom animations
  • Configure springs with stiffness and damping
  • Trigger entrance animations with whileInView
  • Stagger grouped elements at 0.08s intervals
  • Use viewport: { once: true } to animate only on first view
Don't
  • Use CSS @keyframes for custom animations
  • Use Tailwind animate utilities (animate-bounce, etc.) except for skeletons/spinners
  • Animate route transitions (causes SSR hydration jank)
  • Animate data mutation responses (optimistic updates are instant)
  • Use duration / ease when springs are more appropriate