Skip to main content
Animation components define the timing and feel of state transitions. All animation functions return an AnimationComponent that can be customized with modifiers and used with withAnimation.

Animation Types

Spring

Creates a spring-based animation with natural, physics-based motion.
Spring(options?: {
  response?: number
  dampingFraction?: number
  blendDuration?: number
})
Parameters:
  • response (optional) - The stiffness of the spring (higher = faster). Default varies by platform.
  • dampingFraction (optional) - The amount of damping (0 = no damping, 1 = critical damping). Default is typically 1.
  • blendDuration (optional) - Duration in seconds to blend with other animations. Default is 0.
Example:
function BouncingBox() {
  const [scale, setScale] = useState(1.0)

  const bounce = () => {
    withAnimation(Spring({ dampingFraction: 0.6 }), () => {
      setScale(scale === 1.0 ? 1.5 : 1.0)
    })
  }

  return (
    VStack([
      Rectangle()
        .fill(Color("blue"))
        .frame({ width: 100, height: 100 })
        .scaleEffect(scale),
      Button("Bounce", bounce)
    ])
  )
}

InterpolatingSpring

Creates a spring animation with explicit physics parameters for precise control over spring behavior.
InterpolatingSpring(options?: {
  stiffness: number
  damping: number
  mass: number
})
Parameters:
  • stiffness (required) - The spring constant (higher = stiffer spring)
  • damping (required) - The damping coefficient (higher = more resistance)
  • mass (required) - The mass being animated (higher = slower to move)
Example:
function PhysicsAnimation() {
  const [offset, setOffset] = useState(0)

  const animate = () => {
    // Heavy, slow-moving spring
    const heavySpring = InterpolatingSpring({
      stiffness: 50,
      damping: 10,
      mass: 2
    })

    withAnimation(heavySpring, () => {
      setOffset(offset === 0 ? 200 : 0)
    })
  }

  return (
    VStack([
      Circle()
        .fill(Color("red"))
        .frame({ width: 50, height: 50 })
        .offset({ x: offset, y: 0 }),
      Button("Animate", animate)
    ])
  )
}

EaseIn

Creates an animation that starts slowly and accelerates toward the end.
EaseIn(options?: {
  duration?: number
})
Parameters:
  • duration (optional) - Animation duration in seconds. Default is typically 0.35.
Example:
function FadeInView() {
  const [opacity, setOpacity] = useState(0)

  const fadeIn = () => {
    withAnimation(EaseIn({ duration: 1.0 }), () => {
      setOpacity(1.0)
    })
  }

  return (
    VStack([
      Text("Hello World")
        .opacity(opacity),
      Button("Fade In", fadeIn)
    ])
  )
}

EaseOut

Creates an animation that starts quickly and decelerates toward the end.
EaseOut(options?: {
  duration?: number
})
Parameters:
  • duration (optional) - Animation duration in seconds. Default is typically 0.35.
Example:
function SlideOutView() {
  const [offset, setOffset] = useState(0)

  const slideOut = () => {
    withAnimation(EaseOut({ duration: 0.5 }), () => {
      setOffset(300)
    })
  }

  return (
    VStack([
      Rectangle()
        .fill(Color("green"))
        .frame({ width: 100, height: 100 })
        .offset({ x: offset, y: 0 }),
      Button("Slide Out", slideOut)
    ])
  )
}

EaseInOut

Creates an animation that starts slowly, accelerates in the middle, and decelerates at the end.
EaseInOut(options?: {
  duration?: number
})
Parameters:
  • duration (optional) - Animation duration in seconds. Default is typically 0.35.
Example:
function SmoothTransition() {
  const [rotation, setRotation] = useState(0)

  const rotate = () => {
    withAnimation(EaseInOut({ duration: 0.8 }), () => {
      setRotation(rotation + 180)
    })
  }

  return (
    VStack([
      Rectangle()
        .fill(Color("purple"))
        .frame({ width: 100, height: 100 })
        .rotationEffect(rotation),
      Button("Rotate", rotate)
    ])
  )
}

Linear

Creates an animation with constant velocity throughout.
Linear(options?: {
  duration?: number
})
Parameters:
  • duration (optional) - Animation duration in seconds. Default is typically 0.35.
Example:
function ConstantMotion() {
  const [position, setPosition] = useState(0)

  const move = () => {
    withAnimation(Linear({ duration: 2.0 }), () => {
      setPosition(position === 0 ? 400 : 0)
    })
  }

  return (
    VStack([
      Circle()
        .fill(Color("orange"))
        .frame({ width: 40, height: 40 })
        .offset({ x: position, y: 0 }),
      Button("Move", move)
    ])
  )
}

Bouncy

Creates a playful spring animation with extra bounce.
Bouncy(options?: {
  duration?: number
  extraBounce?: number
})
Parameters:
  • duration (optional) - The approximate duration in seconds. Default varies by platform.
  • extraBounce (optional) - Amount of additional bounce (0 = no extra bounce, 1 = maximum bounce). Default is 0.
Example:
function PlayfulAnimation() {
  const [scale, setScale] = useState(1.0)

  const animate = () => {
    withAnimation(Bouncy({ duration: 0.6, extraBounce: 0.4 }), () => {
      setScale(scale === 1.0 ? 1.3 : 1.0)
    })
  }

  return (
    VStack([
      Text("Tap me!")
        .scaleEffect(scale)
        .font("title"),
      Button("Bounce", animate)
    ])
  )
}

Snappy

Creates a quick, responsive spring animation.
Snappy(options?: {
  response?: number
  dampingFraction?: number
  blendDuration?: number
})
Parameters:
  • response (optional) - The stiffness of the spring (higher = faster). Default varies by platform.
  • dampingFraction (optional) - The amount of damping (0 = no damping, 1 = critical damping). Default is typically 1.
  • blendDuration (optional) - Duration in seconds to blend with other animations. Default is 0.
Example:
function QuickResponse() {
  const [isExpanded, setIsExpanded] = useState(false)

  const toggle = () => {
    withAnimation(Snappy(), () => {
      setIsExpanded(!isExpanded)
    })
  }

  return (
    VStack([
      Rectangle()
        .fill(Color("cyan"))
        .frame({ width: 200, height: isExpanded ? 200 : 100 }),
      Button(isExpanded ? "Collapse" : "Expand", toggle)
    ])
  )
}

Animation Modifiers

All animation components support these modifier methods that can be chained together:

delay

Delays the start of the animation.
.delay(seconds: number): AnimationComponent
Example:
const delayedAnimation = Spring().delay(0.5)

withAnimation(delayedAnimation, () => {
  setOpacity(1.0)
})

speed

Adjusts the speed of the animation (multiplier).
.speed(multiplier: number): AnimationComponent
Example:
// Play at half speed
const slowAnimation = EaseInOut({ duration: 1.0 }).speed(0.5)

// Play at double speed
const fastAnimation = Linear({ duration: 2.0 }).speed(2.0)

withAnimation(slowAnimation, () => {
  setScale(2.0)
})

repeatCount

Repeats the animation a specific number of times.
.repeatCount(count: number): AnimationComponent
Example:
const repeatingAnimation = Bouncy().repeatCount(3)

withAnimation(repeatingAnimation, () => {
  setRotation(rotation + 360)
})

repeatForever

Repeats the animation indefinitely.
.repeatForever(autoreverses: boolean): AnimationComponent
Parameters:
  • autoreverses - If true, the animation reverses direction each time it completes
Example:
// Continuous rotation
const spinning = Linear({ duration: 2.0 }).repeatForever(false)

// Pulsing effect (forward and back)
const pulsing = EaseInOut({ duration: 1.0 }).repeatForever(true)

withAnimation(spinning, () => {
  setRotation(360)
})

Combining Modifiers

Modifiers can be chained together for complex animation behaviors:
function ComplexAnimation() {
  const [offset, setOffset] = useState(0)

  const animate = () => {
    // Bounce 5 times, at half speed, starting after 0.3 seconds
    const animation = Bouncy({ extraBounce: 0.3 })
      .delay(0.3)
      .speed(0.5)
      .repeatCount(5)

    withAnimation(animation, () => {
      setOffset(100)
    })
  }

  return (
    VStack([
      Circle()
        .fill(Color("pink"))
        .frame({ width: 60, height: 60 })
        .offset({ x: offset, y: 0 }),
      Button("Start Complex Animation", animate)
    ])
  )
}

Complete Examples

Loading Spinner

function LoadingSpinner() {
  const [rotation, setRotation] = useState(0)

  useEffect(() => {
    const animation = Linear({ duration: 1.0 }).repeatForever(false)
    withAnimation(animation, () => {
      setRotation(360)
    })
  }, [])

  return (
    Circle()
      .stroke({ style: Color("blue"), lineWidth: 3 })
      .frame({ width: 40, height: 40 })
      .rotationEffect(rotation)
  )
}

Pulsing Heart

function PulsingHeart() {
  const [scale, setScale] = useState(1.0)

  useEffect(() => {
    const animation = Spring({ dampingFraction: 0.5 })
      .repeatForever(true)

    withAnimation(animation, () => {
      setScale(1.2)
    })
  }, [])

  return (
    Image({ systemName: "heart.fill" })
      .foregroundStyle(Color("red"))
      .scaleEffect(scale)
  )
}

Staggered List Animation

function StaggeredList() {
  const [items] = useState(['Item 1', 'Item 2', 'Item 3', 'Item 4'])
  const [opacity, setOpacity] = useState(0)

  useEffect(() => {
    items.forEach((_, index) => {
      const animation = EaseIn({ duration: 0.5 })
        .delay(index * 0.1)

      withAnimation(animation, () => {
        setOpacity(1.0)
      })
    })
  }, [])

  return (
    VStack(
      items.map((item, index) =>
        Text(item)
          .opacity(opacity)
          .padding(8)
      )
    )
  )
}

Interactive Gesture Animation

function DraggableBox() {
  const [offset, setOffset] = useState({ x: 0, y: 0 })

  const resetPosition = () => {
    const animation = Spring({
      response: 0.3,
      dampingFraction: 0.7
    })

    withAnimation(animation, () => {
      setOffset({ x: 0, y: 0 })
    })
  }

  return (
    VStack([
      Rectangle()
        .fill(Color("indigo"))
        .frame({ width: 100, height: 100 })
        .offset(offset)
        .onDragGesture({
          onChanged: (value) => setOffset(value.translation),
          onEnded: () => resetPosition()
        }),
      Button("Reset", resetPosition)
    ])
  )
}

Animation Best Practices

  1. Choose the Right Animation Type:
    • Use Spring or Snappy for interactive, gesture-driven animations
    • Use EaseInOut for general transitions
    • Use Linear for continuous motion like loaders
    • Use Bouncy for playful, attention-grabbing effects
  2. Duration Guidelines:
    • Quick interactions: 0.2-0.3 seconds
    • Standard transitions: 0.3-0.5 seconds
    • Dramatic effects: 0.5-1.0 seconds
    • Avoid durations over 1.5 seconds unless necessary
  3. Performance Considerations:
    • Animate transform properties (scale, rotation, offset) for best performance
    • Avoid animating layout properties when possible
    • Use repeatForever sparingly to prevent performance issues
    • Consider reducing animation complexity on lower-end devices
  4. Accessibility:
    • Respect user preferences for reduced motion
    • Provide alternative feedback for users who disable animations
    • Keep critical animations brief and purposeful

Notes

  • Animation behavior may vary slightly between platforms (web, iOS, Android)
  • Default parameter values are platform-specific and optimized for each environment
  • Animations created with these functions work seamlessly with withAnimation
  • Multiple animations on the same property will use the most recently applied animation
  • Spring animations may overshoot their target based on damping settings