Motion System

Motion brings interfaces to life. Our motion system provides a carefully crafted set of durations and easing curves that create smooth, purposeful animations. Use motion to guide attention, provide feedback, and create delightful experiences.

When to Use Motion

  • • State changes (hover, active, disabled)
  • • Page transitions and navigation
  • • Drawing attention to important elements
  • • Providing feedback for user actions
  • • Revealing and hiding content

Motion Best Practices

  • • Keep animations fast (150-500ms)
  • • Use consistent easing curves
  • • Respect prefers-reduced-motion
  • • Animate transform and opacity (performant)
  • • Avoid animating layout properties

Variable Weight Motion

For variable fonts (like Clash Display), use the VariableWeightText component to create a silky-smooth "breathing" effect that animates font weight. This animation uses font-variation-settings for true variable font interpolation.

Performance Optimizations:

  • ✓ Uses true variable font (continuous weight axis from 200-700)
  • ✓ GPU-accelerated with will-change
  • ✓ Custom easing curve [0.45, 0, 0.55, 1] for ultra-smooth motion
  • ✓ Font smoothing (antialiased) for crisp rendering
  • ✓ Centered to prevent layout shifts during weight changes
Clash Display
import { VariableWeightText } from '@opencosmos/ui';

<VariableWeightText minWeight={200} maxWeight={700}>
  Clash Display
</VariableWeightText>

Note: The VariableWeightText component defaults to using Clash Display, but you can override with the fontFamily prop to use any variable font.

Duration Scale

Consistent timing creates rhythm. Choose durations based on the size and complexity of the animation.

instant0ms
Immediate feedback
fast150ms
Micro-interactions
normal300ms
Standard transitions
slow500ms
Complex animations
slower800ms
Dramatic effects

Easing Curves

Easing curves define the acceleration of animations. Different curves create different feelings of motion.

default
Natural, decelerating motion
cubic-bezier(0, 0, 0.2, 1)
spring
Playful, bouncy feel
cubic-bezier(0.16, 1, 0.3, 1)
linear
Constant speed
linear

Interactive Playground

Experiment with different combinations of duration and easing to find the perfect motion.

transition: transform 300ms cubic-bezier(0, 0, 0.2, 1)

Implementation Guide

CSS / Tailwind

For simple transitions and states, use CSS transitions with our motion tokens.

/* Using CSS custom properties */
.button {
  transition: all var(--duration-normal) var(--ease-default);
}

/* Using Tailwind */
<button className="transition-all duration-300 ease-out hover:scale-105">
  Hover me
</button>

Framer Motion

motion.dev →
Recommended

Framer Motion is our recommended animation library for React. It provides declarative animations, gesture support, and excellent TypeScript integration.

When to use Framer Motion:

  • • Component animations and transitions
  • • Page transitions in React apps
  • • Gesture-based interactions (drag, swipe)
  • • Layout animations and shared element transitions
  • • When you need React-aware animations
import { motion } from 'framer-motion';

// Basic animation
<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{
    duration: 0.3, // 300ms = normal
    ease: [0, 0, 0.2, 1] // ease-out
  }}
>
  Content
</motion.div>

// Using design system tokens
<motion.button
  whileHover={{ scale: 1.05 }}
  whileTap={{ scale: 0.95 }}
  transition={{
    duration: 0.15, // fast
    ease: [0.16, 1, 0.3, 1] // spring
  }}
>
  Click me
</motion.button>

GSAP (GreenSock Animation Platform)

gsap.com →
Specialized

GSAP is the industry-standard animation library for complex, timeline-based animations. Use it when you need precise control or advanced animation sequences.

When to use GSAP:

  • • Complex animation timelines and sequences
  • • SVG animations and morphing
  • • Scroll-triggered animations (ScrollTrigger)
  • • When you need maximum performance
  • • Framework-agnostic animations
import { gsap } from 'gsap';

// Basic animation
gsap.to('.element', {
  opacity: 1,
  y: 0,
  duration: 0.3, // 300ms = normal
  ease: 'power2.out' // Similar to ease-out
});

// Timeline sequence
const tl = gsap.timeline();
tl.to('.hero', { opacity: 1, duration: 0.5 })
  .to('.cta', { scale: 1, duration: 0.15 }, '-=0.2')
  .to('.features', {
    y: 0,
    stagger: 0.1, // Stagger items
    duration: 0.3
  });

Accessibility: Respecting User Preferences

Always respect the prefers-reduced-motion media query. Some users experience motion sickness or find animations distracting.

/* CSS approach */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

// React Hook approach (recommended)
import { useMotionPreference } from '@opencosmos/ui';

function MyComponent() {
  const { shouldAnimate, scale } = useMotionPreference();

  // Scale 5 (default) = 1x duration
  // Duration: 0.3 is base duration for this animation
  const duration = shouldAnimate && scale > 0 
    ? 0.3 * (5 / scale) 
    : 0;

  return (
    <motion.div
      initial={shouldAnimate ? { opacity: 0, y: 20 } : false}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration }}
    >
      Content
    </motion.div>
  );
}

Framer Motion Integration

Pre-built animation variants and presets for Framer Motion. Import from @opencosmos/ui/utils

Fade Variants

Simple opacity transitions

import { fadeVariants } from '@opencosmos/ui/utils';

<motion.div
  initial="hidden"
  animate="visible"
  variants={fadeVariants

Slide Variants

Slide in from: Left, Right, Top, Bottom

import { slideVariants } from '@opencosmos/ui/utils';

<motion.div
  initial="hidden"
  animate="visible"
  variants={slideVariants

Scale Variants

Default, Grow, Pop effects

import { scaleVariants } from '@opencosmos/ui/utils';

<motion.div
  initial="hidden"
  animate="visible"
  variants={scaleVariants

Modal Variants

Overlay + Content animations

Rotate Variants

Rotation animations with fade

import { rotateVariants } from '@opencosmos/ui/utils';

<motion.div
  initial="hidden"
  animate="visible"
  variants={rotateVariants

Drawer Variants

Slide-out panels from: Left, Right, Top, Bottom

import { drawerVariants } from '@opencosmos/ui/utils';

<motion.div
  initial="hidden"
  animate="visible"
  variants={drawerVariants

Collapse Variants

Height-based expand/collapse for accordions

import { collapseVariants } from '@opencosmos/ui/utils';

<motion.div
  initial="collapsed"
  animate="expanded"
  variants

Complete Presets

Ready-to-use animation configurations with variants + transitions included:

Available Presets

presets.fade
presets.slideUp
presets.scale
presets.modal
presets.list
import { presets } from '@opencosmos/ui/utils';

// Simple fade animation
<motion.div {...presets.fade}>
  Fades in
</motion

Create Custom Animations

Use the createAnimation helper to build custom variants:

import { createAnimation, transitions, easings } from '@opencosmos/ui/utils';

const customAnimation = createAnimation(