CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-framer-motion

A production-ready motion library for React that provides comprehensive animation and gesture APIs for creating fluid, performant interactions.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

layout-presence.mddocs/

Layout and Presence

Components and hooks for layout animations and enter/exit animations with shared layout transitions.

Capabilities

AnimatePresence Component

Enables exit animations for components leaving the DOM and manages component presence lifecycle.

/**
 * Wrapper component that enables exit animations for its children
 * @param props - AnimatePresence configuration
 * @returns JSX element managing presence animations
 */
function AnimatePresence(props: AnimatePresenceProps): JSX.Element;

interface AnimatePresenceProps {
  /**
   * Child components to manage presence for
   */
  children?: React.ReactNode;
  
  /**
   * Whether to animate on initial mount (default: true)
   */
  initial?: boolean;
  
  /**
   * Custom data passed to all child animations
   */
  custom?: any;
  
  /**
   * Called when all exit animations complete
   */
  onExitComplete?: () => void;
  
  /**
   * Animation mode for multiple children
   * - "sync": All children animate simultaneously (default)
   * - "wait": Wait for exit to complete before animating new children
   * - "popLayout": Exiting children are removed from layout flow immediately
   */
  mode?: "sync" | "wait" | "popLayout";
  
  /**
   * Root element for presence calculations (default: nearest positioned ancestor)
   */
  root?: React.RefObject<Element>;
  
  /**
   * Whether presence affects layout calculations (default: true)
   */
  presenceAffectsLayout?: boolean;
  
  /**
   * Propagate presence context to nested AnimatePresence components
   */
  propagate?: boolean;
  
  /**
   * Exit animation anchor point
   */
  anchorX?: number;
}

Usage Examples:

import { AnimatePresence, motion } from "framer-motion";
import { useState } from "react";

// Basic presence animation
function BasicPresenceExample() {
  const [isVisible, setIsVisible] = useState(true);
  
  return (
    <div>
      <button onClick={() => setIsVisible(!isVisible)}>
        Toggle
      </button>
      
      <AnimatePresence>
        {isVisible && (
          <motion.div
            initial={{ opacity: 0, scale: 0.8 }}
            animate={{ opacity: 1, scale: 1 }}
            exit={{ opacity: 0, scale: 0.8 }}
            transition={{ duration: 0.3 }}
            className="w-32 h-32 bg-blue-500 rounded mt-4"
          >
            Animated content
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
}

// Wait mode for sequential animations
function WaitModeExample() {
  const [currentItem, setCurrentItem] = useState(0);
  const items = ["First", "Second", "Third"];
  
  return (
    <div>
      <button 
        onClick={() => setCurrentItem((prev) => (prev + 1) % items.length)}
      >
        Next Item
      </button>
      
      <AnimatePresence mode="wait" onExitComplete={() => console.log("Exit complete")}>
        <motion.div
          key={currentItem}
          initial={{ x: 300, opacity: 0 }}
          animate={{ x: 0, opacity: 1 }}
          exit={{ x: -300, opacity: 0 }}
          transition={{ duration: 0.5 }}
          className="w-64 h-32 bg-green-500 rounded mt-4 flex items-center justify-center text-white"
        >
          {items[currentItem]}
        </motion.div>
      </AnimatePresence>
    </div>
  );
}

// List with exit animations
function ListPresenceExample() {
  const [items, setItems] = useState([1, 2, 3, 4, 5]);
  
  const removeItem = (id: number) => {
    setItems(items.filter(item => item !== id));
  };
  
  const addItem = () => {
    setItems([...items, Math.max(...items, 0) + 1]);
  };
  
  return (
    <div>
      <button onClick={addItem} className="mb-4 px-4 py-2 bg-blue-500 text-white rounded">
        Add Item
      </button>
      
      <AnimatePresence>
        {items.map(item => (
          <motion.div
            key={item}
            initial={{ opacity: 0, height: 0 }}
            animate={{ opacity: 1, height: "auto" }}
            exit={{ opacity: 0, height: 0 }}
            transition={{ duration: 0.3 }}
            className="bg-gray-200 p-4 mb-2 rounded overflow-hidden"
          >
            <div className="flex justify-between items-center">
              <span>Item {item}</span>
              <button 
                onClick={() => removeItem(item)}
                className="px-2 py-1 bg-red-500 text-white rounded text-sm"
              >
                Remove
              </button>
            </div>
          </motion.div>
        ))}
      </AnimatePresence>
    </div>
  );
}

LayoutGroup Component

Enables shared layout animations between components and coordinates layout transitions.

/**
 * Provides shared layout animation context for its children
 * @param props - LayoutGroup configuration
 * @returns JSX element providing layout group context
 */
function LayoutGroup(props: LayoutGroupProps): JSX.Element;

interface LayoutGroupProps {
  /**
   * Child components to share layout animations
   */
  children: React.ReactNode;
  
  /**
   * Unique identifier for this layout group
   */
  id?: string;
  
  /**
   * Inherit layout group from parent (default: true)
   */
  inherit?: boolean;
}

Usage Examples:

import { LayoutGroup, motion } from "framer-motion";
import { useState } from "react";

// Shared layout animations
function SharedLayoutExample() {
  const [selected, setSelected] = useState(0);
  const items = ["Tab 1", "Tab 2", "Tab 3"];
  
  return (
    <LayoutGroup>
      <div className="flex space-x-2 mb-4">
        {items.map((item, index) => (
          <motion.button
            key={index}
            onClick={() => setSelected(index)}
            className={`px-4 py-2 rounded relative ${
              selected === index ? "text-white" : "text-gray-600"
            }`}
            style={{
              WebkitTapHighlightColor: "transparent"
            }}
          >
            {selected === index && (
              <motion.div
                layoutId="activeTab"
                className="absolute inset-0 bg-blue-500 rounded"
                transition={{ type: "spring", bounce: 0.2, duration: 0.6 }}
              />
            )}
            <span className="relative z-10">{item}</span>
          </motion.button>
        ))}
      </div>
      
      <motion.div
        key={selected}
        initial={{ opacity: 0, y: 20 }}
        animate={{ opacity: 1, y: 0 }}
        className="p-4 bg-gray-100 rounded"
      >
        Content for {items[selected]}
      </motion.div>
    </LayoutGroup>
  );
}

// Card expansion example
function CardExpansionExample() {
  const [selectedId, setSelectedId] = useState<string | null>(null);
  const items = [
    { id: "1", title: "Card 1", subtitle: "First card" },
    { id: "2", title: "Card 2", subtitle: "Second card" },
    { id: "3", title: "Card 3", subtitle: "Third card" }
  ];
  
  return (
    <LayoutGroup>
      <div className="grid grid-cols-3 gap-4">
        {items.map(item => (
          <motion.div
            key={item.id}
            layoutId={item.id}
            onClick={() => setSelectedId(item.id)}
            className="bg-white rounded-lg shadow-md p-4 cursor-pointer"
            whileHover={{ scale: 1.05 }}
          >
            <motion.h3 layoutId={`title-${item.id}`} className="font-bold">
              {item.title}
            </motion.h3>
            <motion.p layoutId={`subtitle-${item.id}`} className="text-gray-600">
              {item.subtitle}
            </motion.p>
          </motion.div>
        ))}
      </div>
      
      {selectedId && (
        <motion.div
          className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          onClick={() => setSelectedId(null)}
        >
          <motion.div
            layoutId={selectedId}
            className="bg-white rounded-lg shadow-lg p-8 max-w-md w-full mx-4"
            onClick={(e) => e.stopPropagation()}
          >
            {items.find(item => item.id === selectedId) && (
              <>
                <motion.h3 
                  layoutId={`title-${selectedId}`} 
                  className="font-bold text-2xl mb-4"
                >
                  {items.find(item => item.id === selectedId)?.title}
                </motion.h3>
                <motion.p 
                  layoutId={`subtitle-${selectedId}`} 
                  className="text-gray-600 mb-4"
                >
                  {items.find(item => item.id === selectedId)?.subtitle}
                </motion.p>
                <p>Expanded content goes here...</p>
                <button 
                  onClick={() => setSelectedId(null)}
                  className="mt-4 px-4 py-2 bg-blue-500 text-white rounded"
                >
                  Close
                </button>
              </>
            )}
          </motion.div>
        </motion.div>
      )}
    </LayoutGroup>
  );
}

Presence Hooks

Hooks for accessing presence state and managing component lifecycle within AnimatePresence.

/**
 * Access presence state within AnimatePresence
 * @returns Tuple of [isPresent, safeToRemove]
 */
function usePresence(): [boolean, () => void];

/**
 * Check if component is currently present
 * @returns Boolean indicating presence state
 */
function useIsPresent(): boolean;

/**
 * Access presence context data
 * @returns Presence context information
 */
function usePresenceData(): PresenceContextData | null;

interface PresenceContextData {
  /**
   * Whether component is present
   */
  isPresent: boolean;
  
  /**
   * Function to call when safe to remove component
   */
  onExitComplete?: () => void;
  
  /**
   * Custom data passed from AnimatePresence
   */
  custom?: any;
  
  /**
   * Whether initial animation should be performed
   */
  initial?: boolean;
}

Usage Examples:

import { usePresence, useIsPresent, motion } from "framer-motion";
import { useEffect } from "react";

// Custom exit animation component
function CustomExitComponent({ children }: { children: React.ReactNode }) {
  const [isPresent, safeToRemove] = usePresence();
  
  useEffect(() => {
    if (!isPresent) {
      // Perform custom exit logic
      console.log("Component is exiting");
      
      // Call safeToRemove when ready to be removed from DOM
      const timer = setTimeout(() => {
        safeToRemove();
      }, 1000);
      
      return () => clearTimeout(timer);
    }
  }, [isPresent, safeToRemove]);
  
  return (
    <motion.div
      animate={isPresent ? "enter" : "exit"}
      variants={{
        enter: { opacity: 1, scale: 1 },
        exit: { opacity: 0, scale: 0.8 }
      }}
      transition={{ duration: 0.5 }}
    >
      {children}
    </motion.div>
  );
}

// Conditional rendering based on presence
function ConditionalComponent() {
  const isPresent = useIsPresent();
  
  return (
    <motion.div
      animate={{ 
        backgroundColor: isPresent ? "#3B82F6" : "#EF4444",
        scale: isPresent ? 1 : 0.9
      }}
      className="p-4 rounded text-white"
    >
      {isPresent ? "I'm here!" : "I'm leaving..."}
    </motion.div>
  );
}

Layout Animation Props

Props for automatic layout animations on size and position changes.

interface LayoutAnimationProps {
  /**
   * Enable automatic layout animations
   * - true: Animate both position and size changes
   * - "position": Animate only position changes
   * - "size": Animate only size changes
   */
  layout?: boolean | "position" | "size";
  
  /**
   * Unique identifier for shared layout animations
   */
  layoutId?: string;
  
  /**
   * Mark element as layout root for optimization
   */
  layoutRoot?: boolean;
  
  /**
   * Enable layout animations during scroll
   */
  layoutScroll?: boolean;
  
  /**
   * Dependencies that trigger layout recalculation
   */
  layoutDependency?: any;
  
  /**
   * Called when layout animation starts
   */
  onLayoutAnimationStart?: () => void;
  
  /**
   * Called when layout animation completes
   */
  onLayoutAnimationComplete?: () => void;
}

Usage Examples:

import { motion } from "framer-motion";
import { useState } from "react";

// Automatic layout animations
function LayoutAnimationExample() {
  const [isExpanded, setIsExpanded] = useState(false);
  
  return (
    <motion.div
      layout
      onClick={() => setIsExpanded(!isExpanded)}
      className={`bg-blue-500 text-white p-4 rounded cursor-pointer ${
        isExpanded ? "w-96 h-48" : "w-32 h-16"
      }`}
      transition={{ type: "spring", damping: 20, stiffness: 300 }}
      onLayoutAnimationStart={() => console.log("Layout animation started")}
      onLayoutAnimationComplete={() => console.log("Layout animation completed")}
    >
      <h3>Click to expand</h3>
      {isExpanded && (
        <motion.p
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          className="mt-2"
        >
          This content appears when expanded!
        </motion.p>
      )}
    </motion.div>
  );
}

// Shared layout ID animations
function SharedLayoutIdExample() {
  const [selectedItem, setSelectedItem] = useState<string | null>(null);
  const items = ["item1", "item2", "item3"];
  
  return (
    <div>
      <div className="grid grid-cols-3 gap-4 mb-4">
        {items.map(item => (
          <motion.div
            key={item}
            layoutId={item}
            onClick={() => setSelectedItem(item)}
            className="bg-green-500 text-white p-4 rounded cursor-pointer"
            whileHover={{ scale: 1.05 }}
          >
            {item}
          </motion.div>
        ))}
      </div>
      
      {selectedItem && (
        <motion.div
          layoutId={selectedItem}
          className="bg-green-500 text-white p-8 rounded text-center"
          onClick={() => setSelectedItem(null)}
        >
          Selected: {selectedItem}
          <p className="mt-2 text-sm">Click to deselect</p>
        </motion.div>
      )}
    </div>
  );
}

Layout Utilities

Utility hooks and functions for advanced layout animation control.

/**
 * Perform instant layout transitions without animation
 * @param callback - Function to execute during instant transition
 */
function useInstantLayoutTransition(): (callback: () => void) => void;

/**
 * Reset projection calculations for layout animations
 */
function useResetProjection(): () => void;

/**
 * Enable instant transitions temporarily
 * @returns Function to trigger instant transition
 */
function useInstantTransition(): (callback: () => void) => void;

/**
 * Disable instant transitions for a callback
 * @param callback - Function to execute with instant transitions disabled
 */
function disableInstantTransitions(callback: () => void): void;

Usage Examples:

import { 
  motion, 
  useInstantLayoutTransition, 
  useResetProjection 
} from "framer-motion";
import { useState } from "react";

function LayoutUtilitiesExample() {
  const [items, setItems] = useState([1, 2, 3, 4, 5]);
  const instantTransition = useInstantLayoutTransition();
  const resetProjection = useResetProjection();
  
  const shuffleItems = () => {
    // Shuffle without animation
    instantTransition(() => {
      setItems([...items].sort(() => Math.random() - 0.5));
    });
  };
  
  const resetLayout = () => {
    // Reset layout calculations
    resetProjection();
  };
  
  return (
    <div>
      <div className="mb-4 space-x-2">
        <button 
          onClick={shuffleItems}
          className="px-4 py-2 bg-blue-500 text-white rounded"
        >
          Instant Shuffle
        </button>
        <button 
          onClick={resetLayout}
          className="px-4 py-2 bg-green-500 text-white rounded"
        >
          Reset Layout
        </button>
      </div>
      
      <div className="grid grid-cols-5 gap-2">
        {items.map(item => (
          <motion.div
            key={item}
            layout
            className="bg-purple-500 text-white p-4 rounded text-center"
            transition={{ type: "spring", damping: 25, stiffness: 300 }}
          >
            {item}
          </motion.div>
        ))}
      </div>
    </div>
  );
}

Advanced Presence Patterns

Staggered Exit Animations:

<AnimatePresence>
  {items.map((item, index) => (
    <motion.div
      key={item.id}
      exit={{ 
        opacity: 0, 
        x: -100,
        transition: { delay: index * 0.1 }
      }}
    >
      {item.content}
    </motion.div>
  ))}
</AnimatePresence>

Nested Presence:

<AnimatePresence>
  {isVisible && (
    <motion.div exit={{ opacity: 0 }}>
      <AnimatePresence mode="wait">
        <motion.div key={currentTab} exit={{ x: -100 }}>
          Tab content
        </motion.div>
      </AnimatePresence>
    </motion.div>
  )}
</AnimatePresence>

Custom Exit Timing:

function CustomExitTiming() {
  const [isPresent, safeToRemove] = usePresence();
  
  useEffect(() => {
    if (!isPresent) {
      // Custom async exit logic
      performAsyncCleanup().then(() => {
        safeToRemove();
      });
    }
  }, [isPresent, safeToRemove]);
  
  // Component implementation...
}

docs

animation-controls.md

configuration.md

dom-utilities.md

gestures.md

index.md

layout-presence.md

motion-components.md

motion-values.md

scroll-view.md

tile.json