npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aphrody/m3-motion

v3.2.2

Published

Material 3 animation plugin powered by Motion (formerly Framer Motion)

Downloads

284

Readme

@aphrody/m3-motion

High-fidelity React animation components for Material Design 3 (M3) motion specifications, powered by Motion (formerly Framer Motion).

This package implements the core Material Design 3 motion patterns—including Container Transform, Shared Axis, Fade Through, Collapse, and Fade—using Google's official spring, easing, and duration tokens.


Installation

bun add @aphrody/m3-motion motion

Ensure you have react (>= 18.0.0) and motion (>= 12.0.0) installed in your project.


1. Container Transform (M3ContainerTransform)

The container transform pattern transitions one UI element to another (e.g., morphing a small Card component into a full-page Detail view).

Key Features

  • Aspect Ratio Preservation: Automatically prevents layout distortion (skewing/stretching) of children during container morphs using layout-position correction.
  • Spec-Compliant Cross-Fade: Fades out the starting content early (0% to 30% of transition) and delays the fade-in of target content (20% to 100% of transition) to avoid messy intermediate states.
  • Pointer Event Management: Prevents inactive, hidden content from intercepting pointer interactions.
  • Flexibility: Supports both the Single-Container Toggle approach (re-rendering within the same container) and the Two-Container Shared Layout approach (different source/target elements linked via layoutId).

Sub-Components

  • M3ContainerTransform: Parent morphing container.
  • M3ContainerTransformStart: Wrapper for starting content (e.g., collapsed card details).
  • M3ContainerTransformEnd: Wrapper for ending content (e.g., expanded page details).

Code Examples

Single-Container Toggle Approach

Best when morphing a single component instance in place using state.

import React, { useState } from "react";
import { 
  M3ContainerTransform, 
  M3ContainerTransformStart, 
  M3ContainerTransformEnd 
} from "@aphrody/m3-motion";

export const MorphingCard = () => {
  const [isExpanded, setIsExpanded] = useState(false);

  return (
    <M3ContainerTransform
      isExpanded={isExpanded}
      speed="default"
      style={{
        width: isExpanded ? 500 : 250,
        height: isExpanded ? 400 : 150,
        borderRadius: isExpanded ? 28 : 16,
        backgroundColor: "var(--md-sys-color-surface-container)",
        cursor: "pointer"
      }}
      onClick={() => setIsExpanded(!isExpanded)}
    >
      {/* Start state (collapsed content) */}
      <M3ContainerTransformStart>
        <div style={{ padding: 16 }}>
          <h3>Collapsed Card</h3>
          <p>Click me to expand...</p>
        </div>
      </M3ContainerTransformStart>

      {/* End state (expanded detail content) */}
      <M3ContainerTransformEnd>
        <div style={{ padding: 24 }}>
          <h2>Detailed View</h2>
          <p>This is the expanded content showing the full detail surface.</p>
          <button onClick={(e) => { e.stopPropagation(); setIsExpanded(false); }}>
            Close
          </button>
        </div>
      </M3ContainerTransformEnd>
    </M3ContainerTransform>
  );
};

Two-Container Shared Layout Approach (linked via layoutId)

Best for page/route transitions or separate DOM elements. Wrap the conditional render in <AnimatePresence> to enable the exit fade-out transition.

import React, { useState } from "react";
import { AnimatePresence } from "motion/react";
import { 
  M3ContainerTransform, 
  M3ContainerTransformStart, 
  M3ContainerTransformEnd 
} from "@aphrody/m3-motion";

export const ListView = () => {
  const [selectedId, setSelectedId] = useState<string | null>(null);

  return (
    <div>
      {/* List of items */}
      {!selectedId && (
        <div className="grid">
          <M3ContainerTransform 
            layoutId="card-item-1" 
            onClick={() => setSelectedId("1")}
            style={{ borderRadius: 16, background: "#eee", width: 200, height: 120 }}
          >
            <M3ContainerTransformStart>
              <div style={{ padding: 12 }}>Card Content 1</div>
            </M3ContainerTransformStart>
          </M3ContainerTransform>
        </div>
      )}

      {/* Expanded detail page */}
      <AnimatePresence>
        {selectedId === "1" && (
          <div className="backdrop">
            <M3ContainerTransform 
              layoutId="card-item-1" 
              style={{ borderRadius: 24, background: "#fff", width: 600, height: 450 }}
            >
              <M3ContainerTransformEnd>
                <div style={{ padding: 24 }}>
                  <h2>Expanded Title 1</h2>
                  <p>Long detail text...</p>
                  <button onClick={() => setSelectedId(null)}>Back to List</button>
                </div>
              </M3ContainerTransformEnd>
            </M3ContainerTransform>
          </div>
        )}
      </AnimatePresence>
    </div>
  );
};

2. Shared Axis (M3SharedAxis)

Used for transitions between UI elements that have a spatial or navigational relationship (e.g., page stepper, tab switching, or dialog overlays). Supports x, y, and z axes.

  • Default enter duration: 300ms (medium2) with emphasizedDecelerate ease.
  • Default exit duration: 200ms (short4) with emphasizedAccelerate ease.

Code Example

import React, { useState } from "react";
import { M3SharedAxis } from "@aphrody/m3-motion";

export const StepWizard = () => {
  const [step, setStep] = useState(0);

  return (
    <div>
      <M3SharedAxis 
        stateKey={step} 
        axis="x" 
        forward={step > 0} 
        style={{ padding: 20 }}
      >
        {step === 0 && <div>Step 1: Enter details</div>}
        {step === 1 && <div>Step 2: Review options</div>}
        {step === 2 && <div>Step 3: Confirm purchase</div>}
      </M3SharedAxis>
      
      <button onClick={() => setStep(prev => Math.max(0, prev - 1))}>Back</button>
      <button onClick={() => setStep(prev => Math.min(2, prev + 1))}>Next</button>
    </div>
  );
};

3. Fade Through (M3FadeThrough)

Used for transitions between primary views that do not have a strong spatial relationship (e.g., bottom navigation switching, settings screens).

  • Default exit duration (fade-out): 90ms with emphasizedAccelerate ease.
  • Default enter duration (fade-in & scale-up from 92%): 210ms with emphasizedDecelerate ease.

Code Example

import React, { useState } from "react";
import { M3FadeThrough } from "@aphrody/m3-motion";

export const AppNavigation = () => {
  const [currentTab, setCurrentTab] = useState("home");

  return (
    <div>
      <M3FadeThrough stateKey={currentTab} style={{ height: 400 }}>
        {currentTab === "home" && <div>Home View Content</div>}
        {currentTab === "search" && <div>Search Page Content</div>}
        {currentTab === "profile" && <div>User Profile Settings</div>}
      </M3FadeThrough>

      <nav>
        <button onClick={() => setCurrentTab("home")}>Home</button>
        <button onClick={() => setCurrentTab("search")}>Search</button>
        <button onClick={() => setCurrentTab("profile")}>Profile</button>
      </nav>
    </div>
  );
};

4. Duration and Easing Presets

Developers have direct access to official Material Design 3 timings and curves:

import { m3Easings, m3Durations, m3Springs } from "@aphrody/m3-motion";

// Custom animation example using M3 tokens
const myCustomTransition = {
  ease: m3Easings.emphasizedDecelerate,
  duration: m3Durations.medium4, // 400ms
};

Motion Presets Summary

  • m3Easings: Bezier curves for emphasized (approx), emphasizedDecelerate (enter), emphasizedAccelerate (exit), standard, standardDecelerate, and standardAccelerate.
  • m3Durations: Timing scales from short1 (50ms) to extraLong4 (1000ms).
  • m3Springs: Hand-tuned spring constants (fast, default, slow) split into spatial (bouncy) and effects (critically damped).

License

Apache-2.0