Option

motion-ui

affaan-m/ECC affaan-m/ECC

Bietet ein produktionsreifes UI-Animationssystem für React/Next.js mit Richtlinien zu Leistung, Barrierefreiheit und Benutzerfreundlichkeit.

...Alle erweitern
0
Zeit aktualisiert 1. Oktober 2026

Motion System v4.2

Produktionsreifes UI-Bewegungssystem für React / Next.js.

Der Fokus liegt auf Leistung, Barrierefreiheit und Benutzerfreundlichkeit – nicht auf Dekoration.

Anwendungsbereiche

Verwenden Sie dieses Bewegungssystem, wenn Bewegungen:

  • die Aufmerksamkeit lenken (z. B. Onboarding, wichtige Aktionen)
  • einen Zustand vermitteln (Ladevorgang, Erfolg, Fehler, Übergänge)
  • die räumliche Kontinuität gewährleistet (Layoutänderungen, Navigation)

Geeignete Szenarien

  • Interaktive Komponenten (Schaltflächen, Modalfenster, Menüs)
  • Zustandsübergänge (Laden → geladen, offen → geschlossen)
  • Navigations- und Layoutkontinuität (gemeinsame Elemente, Überblendungen)

Zu beachtende Aspekte

  • Barrierefreiheit: Unterstützen Sie stets reduzierte Animationen
  • Geräteanpassung: Anpassung an Geräte mit geringer Leistung
  • Leistungskompromisse: Reaktionsgeschwindigkeit hat Vorrang vor visueller Flüssigkeit

Vermeiden Sie die Verwendung von Bewegungen, wenn

  • sie rein dekorativ ist
  • sie die Benutzerfreundlichkeit oder Übersichtlichkeit beeinträchtigt
  • sie sich negativ auf die Leistung auswirkt

So funktioniert es

Grundprinzip

Bewegungen müssen:

  • die Aufmerksamkeit lenken
  • einen Zustand vermitteln
  • die räumliche Kontinuität wahren

Wenn sie keines davon erfüllt → entfernen Sie sie.

Installation

npm install motion

Version

  • motion/react - Standard für aktuelle Motion for React-Projekte (Paket: motion)
  • framer-motion - veralteter Importpfad für Projekte, die noch von Framer Motion abhängig sind

Nicht mischen. Eine Vermischung führt zu Konflikten bei den internen Schedulern und fehlerhaften AnimatePresence Kontexte – Komponenten aus dem einen Paket koordinieren ihre Ausblendanimationen nicht mit Komponenten aus dem anderen.

So überprüfen Sie, welche Version Ihr Projekt verwendet:

cat package.json | grep -E '"motion"|"framer-motion"'

Importieren Sie stets konsequent aus einer Quelle:

// Correct (modern)
import { motion, AnimatePresence } from "motion/react"

// Correct (legacy)
import { motion, AnimatePresence } from "framer-motion"

// Never mix both in the same project

Motion-Token

// motionTokens.ts
export const motionTokens = {
  duration: {
    fast: 0.18,
    normal: 0.35,
    slow: 0.6
  },
  // Use these as the `ease` value inside a `transition` object:
  // transition={{ duration: motionTokens.duration.normal, ease: motionTokens.easing.smooth }}
  easing: {
    smooth: [0.22, 1, 0.36, 1] as [number, number, number, number],
    sharp:  [0.4,  0, 0.2, 1] as [number, number, number, number]
  },
  distance: {
    sm: 8,
    md: 16,
    lg: 24
  }
}

Anwendungsbeispiel:

import { motionTokens } from "@/lib/motionTokens"


Leistungsregeln

Sicher

  • transform
  • Opazität

Vermeiden

  • Breite / Höhe
  • oben / links

Regel: Responsivität > Laufruhe

Geräteanpassung

Die Heuristik kombiniert die Anzahl der CPU-Kerne und den verfügbaren Arbeitsspeicher, um ein zuverlässigeres Signal zu erhalten. deviceMemory ist in Chrome/Android verfügbar; die Fallback-Lösung deckt Safari und Firefox ab.

const isLowEnd =
  typeof navigator !== "undefined" && (
    // Low memory (Chrome/Android only; undefined elsewhere → treat as capable)
    (navigator.deviceMemory !== undefined && navigator.deviceMemory <= 2) ||
    // Few cores AND no memory API (covers Safari/Firefox on weak hardware)
    (navigator.deviceMemory === undefined && navigator.hardwareConcurrency <= 4)
  )

const duration = isLowEnd ? 0.2 : 0.4

Barrierefreiheit

JS (useReducedMotion)

import { motion, useReducedMotion } from "motion/react"

export function FadeIn() {
  const reduce = useReducedMotion()

  return (
    
  )
}

CSS

@media (prefers-reduced-motion: reduce) {
  .motion-safe-transition {
    transition: opacity 0.2s;
  }

  .motion-reduce-transform {
    transform: none !important;
  }
}

Tailwind

Architektur & Muster

Kernmuster

Szenario Muster
Feedback beim Hover whileHover
Feedback beim Antippen/Drücken whileTap
Beim Scrollen anzeigen whileInView
Scroll-gebundener Wert useScroll + useTransform
Bedingtes Ein- und Ausblenden AnimatePresence
Geringe Layoutverschiebungen (einzelnes Element, < ~300px Änderung) layout prop
Große Layoutverschiebungen oder seitenweite Neuanordnungen Vermeiden layout; stattdessen CSS-Übergänge oder Routing auf Seitenebene verwenden
Komplexe, imperativische Abläufe useAnimate

Warum sollte man „layout“ bei großen Containern vermeiden? Die Layout-Animation von Framer nutzt transform , um Positionen abzugleichen, doch bei Elementen, die den gesamten Viewport einnehmen oder einen tiefgreifenden Neuanordnungsvorgang auslösen, verursachen die Messkosten sichtbare Ruckler und CLS. Bevorzugen Sie CSS-Grid-/Flexbox-Übergänge oder koordinieren Sie diese layoutId nur auf bestimmte untergeordnete Elemente.

Layout & Übergänge

  • Gemeinsame Elementübergänge → layoutId (müssen pro eingebundener Instanz eindeutig sein)
  • Ein- und Ausblendübergänge → AnimatePresence (siehe mode Anleitung unten)

AnimatePresence mode

Immer mode ausdrücklich angeben – die Standardeinstellung ("sync") werden „enter“ und „exit“ gleichzeitig ausgeführt, was bei den meisten UI-Mustern zu einer visuellen Überlappung führt.

mode Wann zu verwenden
"wait" „Exit“ ist abgeschlossen, bevor „Enter“ beginnt. Verwenden Sie diese Option für Modale, Toasts und Seitenübergänge.
"sync" (Standard) „Enter“ und „Exit“ überlappen sich. Nur verwenden, wenn die Überlappung beabsichtigt ist (z. B. bei Karussells mit Überblendung).
"popLayout" Das verlassene Element wird sofort aus dem Fluss entfernt; die verbleibenden Elemente füllen den Platz mit einer Animation aus. Verwenden Sie dies für Listen, Registerkarten und ausblendbare Karten.
// Modal — always use "wait"

  {open && }


// Dismissible list item — use "popLayout"

  {items.map(item => )}

Fortgeschrittene Muster (Konzepte)

  • Parallax (scrollabhängige Transformationen)
  • Scroll-Storytelling (fixierte Abschnitte)
  • 3D-Neigung (zeigerbasierte Transformationen)
  • Überblendung (gemeinsamer layoutId)
  • Progressive Enthüllung (Clip-Pfad)
  • Skeleton-Ladeanzeige (zyklische Deckkraft)
  • Mikrointeraktionen (Hover-/Tap-Feedback)
  • Federsystem (physikbasierte Bewegung)

Grundlagen von Modalen

  • Fokus-Falle
  • Schließen
  • Scroll-Sperre
  • ARIA-Rollen
  • Verwendung AnimatePresence mode="wait" damit die Beendigungsanimation abgeschlossen ist, bevor das nächste Modalfenster erscheint

Vollständiges Beispiel

import React, { useEffect, useRef, useState } from "react"
import { motion, AnimatePresence } from "motion/react"

function useFocusTrap(ref: React.RefObject, active: boolean) {
  useEffect(() => {
    if (!active || !ref.current) return
    const el = ref.current
    const focusable = el.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    )
    const first = focusable[0]
    const last  = focusable[focusable.length - 1]

    function handleKey(e: KeyboardEvent) {
      if (e.key !== "Tab") return
      if (e.shiftKey && document.activeElement === first) {
        e.preventDefault()
        last?.focus()
      } else if (!e.shiftKey && document.activeElement === last) {
        e.preventDefault()
        first?.focus()
      }
    }

    el.addEventListener("keydown", handleKey)
    first?.focus()
    return () => el.removeEventListener("keydown", handleKey)
  }, [active, ref])
}

function useScrollLock(active: boolean) {
  useEffect(() => {
    if (!active) return
    const prev = document.body.style.overflow
    document.body.style.overflow = "hidden"
    return () => { document.body.style.overflow = prev }
  }, [active])
}

function Modal({ open, closeModal }: { open: boolean; closeModal: () => void }) {
  const ref = useRef(null)

  useFocusTrap(ref, open)
  useScrollLock(open)

  useEffect(() => {
    function onKey(e: KeyboardEvent) {
      if (e.key === "Escape") closeModal()
    }
    if (open) window.addEventListener("keydown", onKey)
    return () => window.removeEventListener("keydown", onKey)
  }, [open, closeModal])

  return (
    // mode="wait" ensures exit animation finishes before any new modal enters
    
      {open && (
        
          
            
            
      )}
    
  )
}

export function Example() {
  const [open, setOpen] = useState(false)

  return (
    <>
      

SSR-Sicherheit

  • Stimmen Sie die Anfangszustände zwischen Server- und Client-Renderings ab
  • Vermeiden Sie implizite Animationsursprünge (legen Sie initial explizit festlegen)
  • Bewegungskomponenten in "use client" den Next.js-App-Router

Fehlerbehebung

Überprüfen:

  • Falscher Import (Vermischung von motion/react und framer-motion)
  • fehlende "use client" Direktive im Next.js-App-Router
  • Fehlende key Prop bei AnimatePresence Kindelementen
  • Hydration-Diskrepanz (der Anfangszustand unterscheidet sich zwischen SSR und Client)
  • layout Falsche Verwendung von `prop` bei großen Containern, was zu Rucklern beim Reflow führt
  • Zustandsgesteuerte Animation wird nicht ausgelöst (Abhängigkeits-Arrays überprüfen)

QA

  • Kein CLS
  • Tastatur funktioniert
  • Fokus bleibt in Modalen hängen
  • ARIA-Rollen korrekt (role="dialog", aria-modal="true")
  • Reduzierte Bewegung wird berücksichtigt (useReducedMotion + CSS-Medienabfrage)
  • Keine Hydration-Warnungen in Next.js
  • Animationen werden beim Unmount sauber beendet (keine Speicherlecks)
  • AnimatePresence mode Wird an allen Verwendungsstellen explizit gesetzt

Anti-Muster

  • Animierung von Layout-Eigenschaften (width, height, top, left)
  • Endlose Animationen ohne Zweck (immer fragen: Welchen Zustand vermittelt dies?)
  • Übermäßige Staffelung von Listen ( staggerChildren ≤ 0,1 s; darüber hinaus wirkt es langsam)
  • Einstellungen für reduzierte Bewegung ignorieren
  • Verwendung von layout bei großen oder den gesamten Viewport einnehmenden Containern
  • Weglassen von mode bei AnimatePresence (Standard "sync" führt zu visuellen Überlappungen)
  • Bewegung ausschließlich zu dekorativen Zwecken einsetzen

Philosophie

Bewegung ist Interaktionsdesign.

Grundregel

Wenn Bewegung die Benutzererfahrung nicht verbessert → entfernen Sie sie.

Beispiele

Schaltflächeninteraktion

import { motion } from "motion/react"

export function Button() {
  return (
    
      Click me
    
  )
}

Beispiel für reduzierte Bewegung

import { motion, useReducedMotion } from "motion/react"

export function FadeIn() {
  const reduce = useReducedMotion()

  return (
    
  )
}

Versetzte Liste

import { motion } from "motion/react"

const container = {
  hidden: {},
  visible: {
    transition: { staggerChildren: 0.08 } // keep ≤ 0.1s to avoid sluggishness
  }
}

const item = {
  hidden:  { opacity: 0, y: 10 },
  visible: { opacity: 1, y: 0,  transition: { duration: 0.3, ease: [0.22, 1, 0.36, 1] } }
}

export function List() {
  return (
    
      {[1, 2, 3].map(i => (
        Item {i}
      ))}
    
  )
}

Modal mit „AnimatePresence“

import { motion, AnimatePresence } from "motion/react"

export function Modal({ open }: { open: boolean }) {
  return (
    
      {open && (
        
      )}
    
  )
}

Parallax-Scroll

import { useScroll, useTransform, motion } from "motion/react"

export function Parallax() {
  const { scrollYProgress } = useScroll()
  const y = useTransform(scrollYProgress, [0, 1], [0, -80])

  return 
}

Skeleton-Ladeanzeige

import { motion } from "motion/react"

export function Skeleton() {
  return (
    
  )
}

Gemeinsames Layout (Crossfade)

import { motion } from "motion/react"

// layoutId must be unique per mounted instance.
// If multiple instances can exist simultaneously, append a unique id:
// layoutId={`shared-${item.id}`}
export function Shared() {
  return 
}
Auf GitHub ansehen
---
name: motion-ui
description: Provides a production-ready UI motion system for React/Next.js with performance, accessibility, and usability guidelines.
---

# Motion System v4.2

Production-ready UI motion system for React / Next.js.

Focused on **performance, accessibility, and usability** — not decoration.

## When to Use

Use this motion system when motion:

* Guides attention (e.g., onboarding, key actions)
* Communicates state (loading, success, error, transitions)
* Preserves spatial continuity (layout changes, navigation)

### Appropriate Scenarios

* Interactive components (buttons, modals, menus)
* State transitions (loading → loaded, open → closed)
* Navigation and layout continuity (shared elements, crossfade)

### Considerations

* **Accessibility**: Always support reduced motion
* **Device adaptation**: Adjust for low-end devices
* **Performance trade-offs**: Prefer responsiveness over visual smoothness

### Avoid Using Motion When

* It is purely decorative
* It reduces usability or clarity
* It impacts performance negatively

---

## How It Works

### Core Principle

Motion must:

* Guide attention
* Communicate state
* Preserve spatial continuity

If it does none → remove it.

---

### Installation

```bash
npm install motion
```

---

### Version

* `motion/react` - default for current Motion for React projects (package: `motion`)
* `framer-motion` - legacy import path for projects that still depend on Framer Motion

**Do not mix.** Mixing causes conflicting internal schedulers and broken `AnimatePresence` contexts — components from one package will not coordinate exit animations with components from the other.

To check which version your project uses:

```bash
cat package.json | grep -E '"motion"|"framer-motion"'
```

Always import from one source consistently:

```ts
// Correct (modern)
import { motion, AnimatePresence } from "motion/react"

// Correct (legacy)
import { motion, AnimatePresence } from "framer-motion"

// Never mix both in the same project
```

---

### Motion Tokens

```ts
// motionTokens.ts
export const motionTokens = {
  duration: {
    fast: 0.18,
    normal: 0.35,
    slow: 0.6
  },
  // Use these as the `ease` value inside a `transition` object:
  // transition={{ duration: motionTokens.duration.normal, ease: motionTokens.easing.smooth }}
  easing: {
    smooth: [0.22, 1, 0.36, 1] as [number, number, number, number],
    sharp:  [0.4,  0, 0.2, 1] as [number, number, number, number]
  },
  distance: {
    sm: 8,
    md: 16,
    lg: 24
  }
}
```

Usage example:

```tsx
import { motionTokens } from "@/lib/motionTokens"

<motion.div
  initial={{ opacity: 0, y: motionTokens.distance.md }}
  animate={{ opacity: 1, y: 0 }}
  transition={{
    duration: motionTokens.duration.normal,
    ease: motionTokens.easing.smooth
  }}
/>
```

---

### Performance Rules

**Safe**

* transform
* opacity

**Avoid**

* width / height
* top / left

Rule: responsiveness > smoothness

---

### Device Adaptation

The heuristic combines CPU core count **and** available memory for a more reliable signal. `deviceMemory` is available on Chrome/Android; the fallback covers Safari and Firefox.

```ts
const isLowEnd =
  typeof navigator !== "undefined" && (
    // Low memory (Chrome/Android only; undefined elsewhere → treat as capable)
    (navigator.deviceMemory !== undefined && navigator.deviceMemory <= 2) ||
    // Few cores AND no memory API (covers Safari/Firefox on weak hardware)
    (navigator.deviceMemory === undefined && navigator.hardwareConcurrency <= 4)
  )

const duration = isLowEnd ? 0.2 : 0.4
```

---

### Accessibility

#### JS (useReducedMotion)

```tsx
import { motion, useReducedMotion } from "motion/react"

export function FadeIn() {
  const reduce = useReducedMotion()

  return (
    <motion.div
      initial={{ opacity: 0, y: reduce ? 0 : 24 }}
      animate={{ opacity: 1, y: 0 }}
    />
  )
}
```

#### CSS

```css
@media (prefers-reduced-motion: reduce) {
  .motion-safe-transition {
    transition: opacity 0.2s;
  }

  .motion-reduce-transform {
    transform: none !important;
  }
}
```

#### Tailwind

```html
<div class="motion-safe:animate-fade motion-reduce:opacity-100"></div>
```

---

### Architecture & Patterns

#### Core Patterns

| Scenario | Pattern |
|---|---|
| Hover feedback | `whileHover` |
| Tap / press feedback | `whileTap` |
| Reveal on scroll | `whileInView` |
| Scroll-linked value | `useScroll` + `useTransform` |
| Conditional mount/unmount | `AnimatePresence` |
| Small layout shifts (single element, < ~300px change) | `layout` prop |
| Large layout shifts or full-page reflows | Avoid `layout`; use CSS transitions or page-level routing instead |
| Complex, imperative sequences | `useAnimate` |

> **Why avoid `layout` on large containers?** Framer's layout animation uses `transform` to reconcile positions, but on elements that span the full viewport or trigger deep reflow, the measurement cost causes visible jank and CLS. Prefer CSS Grid/Flexbox transitions or coordinate with `layoutId` on specific child elements only.

#### Layout & Transitions

* Shared element transitions → `layoutId` (must be unique per mounted instance)
* Enter / exit transitions → `AnimatePresence` (see `mode` guidance below)

#### AnimatePresence `mode`

Always specify `mode` explicitly — the default (`"sync"`) runs enter and exit simultaneously, which causes visual overlap in most UI patterns.

| `mode` | When to use |
|---|---|
| `"wait"` | Exit completes before enter starts. Use for **modals, toasts, page transitions**. |
| `"sync"` (default) | Enter and exit overlap. Use only when overlap is intentional (e.g., crossfade carousels). |
| `"popLayout"` | Exiting element is popped out of flow immediately; remaining items animate to fill. Use for **lists, tabs, dismissible cards**. |

```tsx
// Modal — always use "wait"
<AnimatePresence mode="wait">
  {open && <Modal key="modal" />}
</AnimatePresence>

// Dismissible list item — use "popLayout"
<AnimatePresence mode="popLayout">
  {items.map(item => <Card key={item.id} />)}
</AnimatePresence>
```

---

### Advanced Patterns (Concepts)

* Parallax (scroll-linked transforms)
* Scroll storytelling (sticky sections)
* 3D tilt (pointer-based transforms)
* Crossfade (shared `layoutId`)
* Progressive reveal (clip-path)
* Skeleton loading (looped opacity)
* Micro-interactions (hover/tap feedback)
* Spring system (physics-based motion)

---

### Modal Essentials

* Focus trap
* Escape close
* Scroll lock
* ARIA roles
* Use `AnimatePresence mode="wait"` so exit animation completes before the next modal enters

#### Full Example

```tsx
import React, { useEffect, useRef, useState } from "react"
import { motion, AnimatePresence } from "motion/react"

function useFocusTrap(ref: React.RefObject<HTMLDivElement | null>, active: boolean) {
  useEffect(() => {
    if (!active || !ref.current) return
    const el = ref.current
    const focusable = el.querySelectorAll<HTMLElement>(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    )
    const first = focusable[0]
    const last  = focusable[focusable.length - 1]

    function handleKey(e: KeyboardEvent) {
      if (e.key !== "Tab") return
      if (e.shiftKey && document.activeElement === first) {
        e.preventDefault()
        last?.focus()
      } else if (!e.shiftKey && document.activeElement === last) {
        e.preventDefault()
        first?.focus()
      }
    }

    el.addEventListener("keydown", handleKey)
    first?.focus()
    return () => el.removeEventListener("keydown", handleKey)
  }, [active, ref])
}

function useScrollLock(active: boolean) {
  useEffect(() => {
    if (!active) return
    const prev = document.body.style.overflow
    document.body.style.overflow = "hidden"
    return () => { document.body.style.overflow = prev }
  }, [active])
}

function Modal({ open, closeModal }: { open: boolean; closeModal: () => void }) {
  const ref = useRef<HTMLDivElement>(null)

  useFocusTrap(ref, open)
  useScrollLock(open)

  useEffect(() => {
    function onKey(e: KeyboardEvent) {
      if (e.key === "Escape") closeModal()
    }
    if (open) window.addEventListener("keydown", onKey)
    return () => window.removeEventListener("keydown", onKey)
  }, [open, closeModal])

  return (
    // mode="wait" ensures exit animation finishes before any new modal enters
    <AnimatePresence mode="wait">
      {open && (
        <motion.div
          role="dialog"
          aria-modal="true"
          aria-labelledby="modal-title"
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
          transition={{ duration: 0.2 }}
          className="fixed inset-0 flex items-center justify-center bg-black/40"
        >
          <motion.div
            ref={ref}
            initial={{ scale: 0.95, opacity: 0 }}
            animate={{ scale: 1,    opacity: 1 }}
            exit={{    scale: 0.95, opacity: 0 }}
            transition={{ duration: 0.2, ease: [0.22, 1, 0.36, 1] }}
            className="bg-white p-6 rounded"
          >
            <h2 id="modal-title">Dialog Title</h2>
            <button onClick={closeModal}>Close</button>
          </motion.div>
        </motion.div>
      )}
    </AnimatePresence>
  )
}

export function Example() {
  const [open, setOpen] = useState(false)

  return (
    <>
      <button onClick={() => setOpen(true)}>Open</button>
      <Modal open={open} closeModal={() => setOpen(false)} />
    </>
  )
}
```

---

### SSR Safety

* Match initial states between server and client renders
* Avoid implicit animation origins (always set `initial` explicitly)
* Wrap motion components in `"use client"` in Next.js App Router

---

### Debugging

Check:

* Wrong import (mixing `motion/react` and `framer-motion`)
* Missing `"use client"` directive in Next.js App Router
* Missing `key` prop on `AnimatePresence` children
* Hydration mismatch (initial state differs between SSR and client)
* `layout` prop misuse on large containers causing reflow jank
* State-driven animation not triggering (check dependency arrays)

---

### QA

* No CLS
* Keyboard works
* Focus trapped in modals
* ARIA roles correct (`role="dialog"`, `aria-modal="true"`)
* Reduced motion respected (`useReducedMotion` + CSS media query)
* No hydration warnings in Next.js
* Animations stop cleanly on unmount (no memory leaks)
* `AnimatePresence mode` set explicitly on all usage sites

---

### Anti-Patterns

* Animating layout properties (`width`, `height`, `top`, `left`)
* Infinite animations without purpose (always ask: what state does this communicate?)
* Over-staggering lists (keep `staggerChildren` ≤ 0.1s; beyond that it feels slow)
* Ignoring reduced motion preferences
* Using `layout` on large or full-viewport containers
* Omitting `mode` on `AnimatePresence` (default `"sync"` causes visual overlap)
* Using motion purely for decoration

---

### Philosophy

Motion is interaction design.

---

### Final Rule

> If motion does not improve UX → remove it.

---

## Examples

### Button Interaction

```tsx
import { motion } from "motion/react"

export function Button() {
  return (
    <motion.button
      whileHover={{ scale: 1.02 }}
      whileTap={{ scale: 0.97 }}
      transition={{ duration: 0.15, ease: [0.4, 0, 0.2, 1] }}
    >
      Click me
    </motion.button>
  )
}
```

---

### Reduced Motion Example

```tsx
import { motion, useReducedMotion } from "motion/react"

export function FadeIn() {
  const reduce = useReducedMotion()

  return (
    <motion.div
      initial={{ opacity: 0, y: reduce ? 0 : 24 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: reduce ? 0.1 : 0.35, ease: [0.22, 1, 0.36, 1] }}
    />
  )
}
```

---

### Stagger List

```tsx
import { motion } from "motion/react"

const container = {
  hidden: {},
  visible: {
    transition: { staggerChildren: 0.08 } // keep ≤ 0.1s to avoid sluggishness
  }
}

const item = {
  hidden:  { opacity: 0, y: 10 },
  visible: { opacity: 1, y: 0,  transition: { duration: 0.3, ease: [0.22, 1, 0.36, 1] } }
}

export function List() {
  return (
    <motion.ul variants={container} initial="hidden" animate="visible">
      {[1, 2, 3].map(i => (
        <motion.li key={i} variants={item}>Item {i}</motion.li>
      ))}
    </motion.ul>
  )
}
```

---

### Modal with AnimatePresence

```tsx
import { motion, AnimatePresence } from "motion/react"

export function Modal({ open }: { open: boolean }) {
  return (
    <AnimatePresence mode="wait">
      {open && (
        <motion.div
          initial={{ opacity: 0, scale: 0.95 }}
          animate={{ opacity: 1, scale: 1    }}
          exit={{    opacity: 0, scale: 0.95 }}
          transition={{ duration: 0.2, ease: [0.22, 1, 0.36, 1] }}
        />
      )}
    </AnimatePresence>
  )
}
```

---

### Scroll Parallax

```tsx
import { useScroll, useTransform, motion } from "motion/react"

export function Parallax() {
  const { scrollYProgress } = useScroll()
  const y = useTransform(scrollYProgress, [0, 1], [0, -80])

  return <motion.div style={{ y }} />
}
```

---

### Skeleton Loading

```tsx
import { motion } from "motion/react"

export function Skeleton() {
  return (
    <motion.div
      className="bg-gray-200 h-6 w-full rounded"
      animate={{ opacity: [0.5, 1, 0.5] }}
      transition={{
        duration: 1.5,       // comfortable pulse — was missing, caused fast flash
        repeat: Infinity,
        ease: "easeInOut"
      }}
    />
  )
}
```

---

### Shared Layout (Crossfade)

```tsx
import { motion } from "motion/react"

// layoutId must be unique per mounted instance.
// If multiple instances can exist simultaneously, append a unique id:
// layoutId={`shared-${item.id}`}
export function Shared() {
  return <motion.div layoutId="shared" />
}
```

Alle Dateien

1 Dateien

motion-ui installieren

Laden Sie die Skill-Dateien herunter und entpacken Sie sie in Ihr Verzeichnis „.claude/skills/“.

ZIP herunterladen

Klonen Sie das Repository und kopieren Sie die Skill-Dateien in Ihr Projekt.

git clone https://github.com/affaan-m/ECC/tree/main/skills/motion-ui # Copy SKILL.md to your .claude/skills/ directory

Kopieren Kopieren
Schnelle Einrichtung: Kopiere den Skill-Ordner nach „.claude/skills/“. Claude erkennt den Skill automatisch und nutzt ihn.
Repository affaan-m/ECC

Ähnliche Skills

github-code-search
Zeit aktualisiert 29. Juni 2026
drizzle-orm
Zeit aktualisiert 29. Juni 2026
prisma-client-api
Zeit aktualisiert 29. Juni 2026
clickhouse-io
Zeit aktualisiert 29. Juni 2026
OR