FoundryKit

Best Practices

Best practices for using FoundryKit animations effectively

Best Practices

Learn the best practices for using FoundryKit animations to create engaging, performant, and accessible user experiences.

Animation Usage

Choose Appropriate Animations

Select animations that enhance the user experience without being distracting.

import { FadeIn, SlideUp, Scale } from '@foundrykit/animation'

function AppropriateAnimations() {
  return (
    <div className="space-y-6">
      {/* ✅ Good - Subtle entrance animation */}
      <FadeIn>
        <div className="p-4 bg-blue-100 rounded">
          <h2 className="text-xl font-bold">Welcome</h2>
          <p>Content fades in smoothly</p>
        </div>
      </FadeIn>
      
      {/* ✅ Good - Directional animation for lists */}
      <SlideUp>
        <div className="space-y-2">
          {['Item 1', 'Item 2', 'Item 3'].map((item, index) => (
            <div key={index} className="p-2 bg-gray-100 rounded">
              {item}
            </div>
          ))}
        </div>
      </SlideUp>
      
      {/* ✅ Good - Interactive feedback */}
      <Scale>
        <button className="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700">
          Click me
        </button>
      </Scale>
    </div>
  )
}

// ❌ Avoid - Overly complex or distracting animations
function AvoidExcessiveAnimations() {
  return (
    <div className="space-y-4">
      {/* Too many animations can be overwhelming */}
      <FadeIn>
        <SlideUp>
          <Scale>
            <div className="p-4 bg-red-100 rounded">
              Too many nested animations
            </div>
          </Scale>
        </SlideUp>
      </FadeIn>
    </div>
  )
}

Use Consistent Timing

Maintain consistent animation timing throughout your application.

import { FadeIn, SlideUp } from '@foundrykit/animation'

function ConsistentTiming() {
  // Define consistent timing values
  const timing = {
    fast: 0.2,
    normal: 0.4,
    slow: 0.6
  }

  return (
    <div className="space-y-4">
      {/* Fast animations for immediate feedback */}
      <FadeIn duration={timing.fast}>
        <button className="px-4 py-2 bg-blue-600 text-white rounded">
          Quick action
        </button>
      </FadeIn>
      
      {/* Normal animations for content transitions */}
      <SlideUp duration={timing.normal}>
        <div className="p-4 bg-gray-100 rounded">
          Content transition
        </div>
      </SlideUp>
      
      {/* Slow animations for dramatic effects */}
      <FadeIn duration={timing.slow}>
        <div className="p-6 bg-white rounded-lg shadow">
          Hero content
        </div>
      </FadeIn>
    </div>
  )
}

Performance Optimization

Optimize Animation Performance

Follow performance best practices for smooth animations.

import { FadeIn, Stagger } from '@foundrykit/animation'
import { useMediaQuery } from '@foundrykit/hooks'

function OptimizedAnimations() {
  const isMobile = useMediaQuery('(max-width: 768px)')
  const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)')

  // Reduce animation complexity on mobile and for users who prefer reduced motion
  const animationProps = {
    duration: isMobile || prefersReducedMotion ? 0.3 : 0.5,
    staggerDelay: isMobile ? 0.05 : 0.1
  }

  return (
    <div className="space-y-4">
      <FadeIn {...animationProps}>
        <div className="p-4 bg-blue-100 rounded">
          Optimized animation
        </div>
      </FadeIn>
      
      <Stagger {...animationProps}>
        <div className="space-y-2">
          {[1, 2, 3, 4, 5].map(i => (
            <div key={i} className="p-2 bg-gray-100 rounded">
              Item {i}
            </div>
          ))}
        </div>
      </Stagger>
    </div>
  )
}

Limit Concurrent Animations

Avoid overwhelming users with too many simultaneous animations.

import { Stagger } from '@foundrykit/animation'

function LimitedConcurrentAnimations() {
  const largeList = Array.from({ length: 50 }, (_, i) => `Item ${i + 1}`)

  return (
    <div className="space-y-4">
      {/* Limit visible animations to prevent performance issues */}
      <Stagger 
        animation="slideUp" 
        staggerDelay={0.05}
        maxVisible={10 // Only animate visible items
      >
        {largeList.map((item, index) => (
          <div key={index} className="p-2 bg-gray-100 rounded">
            {item}
          </div>
        ))}
      </Stagger>
    </div>
  )
}

Accessibility

Respect User Preferences

Always respect user accessibility preferences.

import { FadeIn, SlideUp } from '@foundrykit/animation'
import { useMediaQuery } from '@foundrykit/hooks'

function AccessibleAnimations() {
  const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)')

  if (prefersReducedMotion) {
    return (
      <div className="space-y-4">
        {/* No animations for users who prefer reduced motion */}
        <div className="p-4 bg-blue-100 rounded">
          <h2 className="text-xl font-bold">Welcome</h2>
          <p>Content without animations</p>
        </div>
      </div>
    )
  }

  return (
    <div className="space-y-4">
      <FadeIn>
        <div className="p-4 bg-blue-100 rounded">
          <h2 className="text-xl font-bold">Welcome</h2>
          <p>Content with animations</p>
        </div>
      </FadeIn>
    </div>
  )
}

Provide Alternative Content

Ensure content is accessible even without animations.

import { FadeIn } from '@foundrykit/animation'

function AlternativeContent() {
  return (
    <div className="space-y-4">
      <FadeIn>
        <div className="p-4 bg-green-100 rounded">
          <h2 className="text-xl font-bold">Important Information</h2>
          <p>This content is important and should be visible regardless of animation support.</p>
        </div>
      </FadeIn>
      
      {/* Fallback for users without JavaScript */}
      <noscript>
        <div className="p-4 bg-yellow-100 rounded border-l-4 border-yellow-500">
          <p>JavaScript is required for enhanced animations.</p>
        </div>
      </noscript>
    </div>
  )
}

State Management

Handle Animation States

Properly manage animation states and transitions.

import { FadeIn, SlideUp } from '@foundrykit/animation'
import { useState, useEffect } from 'react'

function AnimationStateManagement() {
  const [isVisible, setIsVisible] = useState(false)
  const [isLoading, setIsLoading] = useState(true)

  useEffect(() => {
    // Simulate loading
    const timer = setTimeout(() => {
      setIsLoading(false)
      setIsVisible(true)
    }, 1000)

    return () => clearTimeout(timer)
  }, [])

  if (isLoading) {
    return (
      <div className="p-4 bg-gray-100 rounded">
        <div className="animate-pulse">Loading...</div>
      </div>
    )
  }

  return (
    <div className="space-y-4">
      {isVisible && (
        <FadeIn>
          <div className="p-4 bg-blue-100 rounded">
            <h2 className="text-xl font-bold">Content Loaded</h2>
            <p>Animation triggered after loading</p>
          </div>
        </FadeIn>
      )}
      
      <SlideUp>
        <div className="p-4 bg-green-100 rounded">
          <p>Always visible content</p>
        </div>
      </SlideUp>
    </div>
  )
}

Error Handling

Handle animation errors gracefully.

import { FadeIn } from '@foundrykit/animation'
import { useState } from 'react'

function AnimationErrorHandling() {
  const [hasError, setHasError] = useState(false)

  const handleAnimationError = (error: Error) => {
    console.error('Animation error:', error)
    setHasError(true)
  }

  if (hasError) {
    return (
      <div className="p-4 bg-red-100 rounded border-l-4 border-red-500">
        <p>Animation failed to load. Content is still accessible.</p>
      </div>
    )
  }

  return (
    <FadeIn onError={handleAnimationError}>
      <div className="p-4 bg-blue-100 rounded">
        <h2 className="text-xl font-bold">Animated Content</h2>
        <p>This animation has error handling</p>
      </div>
    </FadeIn>
  )
}

Code Organization

Create Reusable Animation Components

Build reusable animation components for consistency.

import { FadeIn, SlideUp, Scale } from '@foundrykit/animation'
import { ReactNode } from 'react'

interface AnimatedCardProps {
  children: ReactNode
  animation?: 'fade' | 'slide' | 'scale'
  delay?: number
  className?: string
}

function AnimatedCard({ 
  children, 
  animation = 'fade', 
  delay = 0, 
  className = '' 
}: AnimatedCardProps) {
  const baseClasses = "p-4 bg-white rounded-lg shadow"
  const combinedClasses = `${baseClasses} ${className}`

  switch (animation) {
    case 'slide':
      return (
        <SlideUp delay={delay}>
          <div className={combinedClasses}>{children}</div>
        </SlideUp>
      )
    case 'scale':
      return (
        <Scale delay={delay}>
          <div className={combinedClasses}>{children}</div>
        </Scale>
      )
    default:
      return (
        <FadeIn delay={delay}>
          <div className={combinedClasses}>{children}</div>
        </FadeIn>
      )
  }
}

function ReusableAnimations() {
  return (
    <div className="space-y-4">
      <AnimatedCard animation="fade">
        <h2 className="text-xl font-bold">Fade Animation</h2>
        <p>Reusable component with fade animation</p>
      </AnimatedCard>
      
      <AnimatedCard animation="slide" delay={0.1}>
        <h2 className="text-xl font-bold">Slide Animation</h2>
        <p>Reusable component with slide animation</p>
      </AnimatedCard>
      
      <AnimatedCard animation="scale" delay={0.2}>
        <h2 className="text-xl font-bold">Scale Animation</h2>
        <p>Reusable component with scale animation</p>
      </AnimatedCard>
    </div>
  )
}

Organize Animation Logic

Separate animation logic from component logic.

import { FadeIn, SlideUp } from '@foundrykit/animation'
import { useMediaQuery } from '@foundrykit/hooks'

// Custom hook for animation configuration
function useAnimationConfig() {
  const isMobile = useMediaQuery('(max-width: 768px)')
  const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)')

  return {
    enabled: !prefersReducedMotion,
    duration: isMobile ? 0.3 : 0.5,
    staggerDelay: isMobile ? 0.05 : 0.1
  }
}

function OrganizedAnimations() {
  const animationConfig = useAnimationConfig()

  if (!animationConfig.enabled) {
    return (
      <div className="space-y-4">
        <div className="p-4 bg-blue-100 rounded">
          <h2 className="text-xl font-bold">Content</h2>
          <p>No animations for accessibility</p>
        </div>
      </div>
    )
  }

  return (
    <div className="space-y-4">
      <FadeIn duration={animationConfig.duration}>
        <div className="p-4 bg-blue-100 rounded">
          <h2 className="text-xl font-bold">Animated Content</h2>
          <p>Organized animation logic</p>
        </div>
      </FadeIn>
      
      <SlideUp 
        duration={animationConfig.duration} 
        delay={animationConfig.staggerDelay}
      >
        <div className="p-4 bg-green-100 rounded">
          <p>Consistent timing</p>
        </div>
      </SlideUp>
    </div>
  )
}

Testing

Test Animation Behavior

Ensure animations work correctly across different scenarios.

import { FadeIn } from '@foundrykit/animation'
import { render, screen, waitFor } from '@testing-library/react'

function TestableAnimation() {
  return (
    <FadeIn data-testid="animated-element">
      <div className="p-4 bg-blue-100 rounded">
        <h2 className="text-xl font-bold">Testable Content</h2>
        <p>This component is testable</p>
      </div>
    </FadeIn>
  )
}

// Example test
describe('TestableAnimation', () => {
  it('renders animated content', async () => {
    render(<TestableAnimation />)
    
    const element = screen.getByTestId('animated-element')
    expect(element).toBeInTheDocument()
    
    // Wait for animation to complete
    await waitFor(() => {
      expect(element).toHaveStyle({ opacity: '1' })
    })
  })
})

Test Accessibility

Verify animations respect accessibility preferences.

import { FadeIn } from '@foundrykit/animation'
import { useMediaQuery } from '@foundrykit/hooks'

function AccessibleTestableAnimation() {
  const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)')

  return (
    <div data-testid="animation-container">
      {prefersReducedMotion ? (
        <div className="p-4 bg-blue-100 rounded" data-testid="static-content">
          <h2 className="text-xl font-bold">Static Content</h2>
          <p>No animations for accessibility</p>
        </div>
      ) : (
        <FadeIn data-testid="animated-content">
          <div className="p-4 bg-blue-100 rounded">
            <h2 className="text-xl font-bold">Animated Content</h2>
            <p>Animations enabled</p>
          </div>
        </FadeIn>
      )}
    </div>
  )
}

Common Anti-Patterns

Avoid These Practices

// ❌ Don't nest multiple animations unnecessarily
function AvoidNestedAnimations() {
  return (
    <FadeIn>
      <SlideUp>
        <Scale>
          <div className="p-4 bg-red-100 rounded">
            Too many nested animations
          </div>
        </Scale>
      </SlideUp>
    </FadeIn>
  )
}

// ❌ Don't ignore user preferences
function AvoidIgnoringPreferences() {
  return (
    <FadeIn>
      <div className="p-4 bg-red-100 rounded">
        Always animated (ignores user preferences)
      </div>
    </FadeIn>
  )
}

// ❌ Don't use animations for critical content
function AvoidCriticalContentAnimations() {
  return (
    <FadeIn>
      <div className="p-4 bg-red-100 rounded">
        <h1 className="text-2xl font-bold">Critical Error</h1>
        <p>This should be immediately visible</p>
      </div>
    </FadeIn>
  )
}

// ❌ Don't forget error handling
function AvoidNoErrorHandling() {
  return (
    <FadeIn>
      <div className="p-4 bg-blue-100 rounded">
        No error handling for animation failures
      </div>
    </FadeIn>
  )
}

Best Practices Checklist

Before Implementation

  • Choose animations that enhance UX, not distract from it
  • Respect user accessibility preferences
  • Test on different devices and performance levels
  • Plan for error states and fallbacks
  • Consider performance implications

During Development

  • Use consistent timing and easing
  • Implement proper cleanup and error handling
  • Test with reduced motion preferences
  • Monitor performance and frame rates
  • Ensure content is accessible without animations

Before Production

  • Test on low-end devices
  • Verify accessibility compliance
  • Check for memory leaks
  • Optimize animation performance
  • Document animation patterns and usage

Next Steps