Communitygithub.com

モーションパターン

React / Next.js向けのプロダクションレディなアニメーションパターン集。ボタン、モーダル、トースト、スタガー、ページ遷移、終了アニメーション、スクロール、レイアウトなどをカバーし、すべてmotion-foundationsのトークンとスプリングに基づいて構築されています。ReactまたはNext.jsで特定のUI要素をアニメーション化する際に使用します。

モーションパターン とは?

このスキルは、React / Next.jsアプリケーションで最も一般的なUIアニメーションのニーズに対応する、コピペ可能なモーションパターンを提供します。すべてのパターンはmotion-foundationsライブラリのトークンとスプリングをインポートして使用するため、生の数値や新しいduration/easing値を定義する必要はありません。AnimatePresenceを使用した条件付きレンダリングの適切な終了アニメーション、Next.js App Router用のページ遷移ラッパー、useScroll + useTransformを用いたスクロール連動パターン、layout/layoutIdによるレイアウトアニメーションなど、アクセシブルでSSRセーフなアニメーションを生成します。また、キーボード操作やARIA属性を考慮したモーダルの実装例など、アクセシビリティとベストプラクティスを重視しています。

対応~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/motion-patterns

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

モーションパターン は何をしますか?

最も一般的なUIアニメーションニーズに対応するコピペパターン集です。 ここにあるパターンはすべて motion-foundations のトークンとスプリングに基づいています。 新しいdurationやeasingの値をここで定義しないでください。必ずインポートしてください。

アクティベートするタイミング

  • ボタン、カード、モーダル、トースト通知をアニメーション化するとき
  • スタガーを使ったリストの登場アニメーションを構築するとき
  • Next.js App Routerでページ遷移を設定するとき
  • 条件付きコンテンツに登場・終了アニメーションを追加するとき
  • スクロールリビール、スクロール連動プログレス、スティッキーストーリーセクションを実装するとき
  • 展開カード、アコーディオン、共有要素遷移を構築するとき

出力

このスキルは次のものを生成します:

  • アクセシブルでSSRセーフな、すべての標準UIコンポーネントのアニメーション
  • 正しい終了動作を持つ AnimatePresence でラップされた条件付きレンダリング
  • Next.js App Router用のページ遷移ラッパーコンポーネント
  • useScroll + useTransform を使用したスクロールリビールおよびスクロール連動パターン
  • 展開やクロスフェード要素のためのレイアウトアニメーションパターン(layout、layoutId)

原則

  • すべてのパターンは motion-foundations からインポートします。生の数値は使いません。
  • すべての条件付きレンダリングは、AnimatePresence でラップし、key を指定します。
  • 終了アニメーションは常に登場アニメーションと一緒に定義します。後付けは禁止です。
  • layout は小さな独立した移動にのみ使用します。大きなサブツリーには明示的なトランスフォームを使用します。

ルール

  1. 条件付きレンダリングは常に AnimatePresence でラップし、直接の子要素に key を指定すること。keyがないと終了アニメーションは決して発火しません。
  2. initial + animate を定義したら必ず exit も定義すること。終了アニメーションがないアニメーションは不完全です。
  3. ページ遷移には mode="wait" を使用すること。終了アニメーションが完了するまで登場アニメーションを開始してはいけません。
  4. 子要素が5つ以上あるサブツリーや、深くネストされたDOMには layout を使用しないこと。代わりに明示的な x/y トランスフォームを使用してください。
  5. スタガーの間隔は 0.05s から 0.10s の間で維持すること。これより短いと機械的に、長いと鈍く感じられます。
  6. モーダルには常に以下を含めること: フォーカストラップ、Escapeキーによる閉じる、スクロールロック、role="dialog"、aria-modal="true"。
  7. スクロールリビールには viewport={{ once: true }} を使用すること。スクロールアウト時に繰り返すのは、情報提供ではなく気が散ります。
  8. すべてのトークン値は motion-foundations からインポートすること。インラインの数値は禁止です。

決定ガイダンス

適切なパターンの選択

状況パターン
要素の表示 / 非表示AnimatePresence
アイテムが順番に読み込まれるリストスタガーバリアント
ルート間の移動ページ遷移ラッパー
要素がその場でサイズ変更するlayout プロパティ
同じ要素がページコンテキストを超えて移動するlayoutId
スクロールしてビューに入ったときに要素が登場するwhileInView
スクロール位置に紐づく値useScroll + useTransform

mode="wait" と mode="sync" の使い分け

モード使用タイミング
waitページ遷移、コンテンツの入れ替え(一度に一つ)
sync積み上げられた通知、リストアイテム(重なりは問題ない)
popLayoutリフローリストからアイテムが削除される場合

コアコンセプト

AnimatePresence 契約

次の3つは常に真でなければなりません:

  1. AnimatePresence が条件をラップしている
  2. 直接の子要素に key がある
  3. 子要素に exit プロパティがある

これらのうち1つでも欠けると、終了アニメーションは静かに失敗します。

layout と layoutId の違い

  • layout — 要素自身のサイズ/位置の変化をその場でアニメーション化する
  • layoutId — 2つの異なる要素をつなぎ、レンダリング間でそれらをクロスフェードさせる

展開するコンテナ内のテキストには layout="position" を使用して、テキストのリフローがアニメーション化されるのを防ぎます。

コード例

ボタンフィードバック

"use client"
import { motion } from "motion/react"
import { springs, motionTokens } from "@/lib/motion-tokens"

<motion.button
  whileHover={{ scale: motionTokens.scale.pop }}
  whileTap={{ scale: motionTokens.scale.press }}
  transition={springs.snappy}
/>

スタガーリスト

"use client"
import { motion } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"

const container = {
  hidden: {},
  visible: {
    transition: {
      staggerChildren: 0.08,   // 0.05–0.10 ルールの範囲内
      delayChildren: 0.1,
    },
  },
}

const item = {
  hidden:  { opacity: 0, y: motionTokens.distance.md },
  visible: { opacity: 1, y: 0, transition: springs.gentle },
}

<motion.ul variants={container} initial="hidden" animate="visible">
  {items.map((i) => (
    <motion.li key={i.id} variants={item} />
  ))}
</motion.ul>

モーダル

"use client"
import { motion, AnimatePresence } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"

// 呼び出し元でラップする:
// <AnimatePresence>{isOpen && <Modal key="modal" />}</AnimatePresence>

export function Modal({ onClose }: { onClose: () => void }) {
  return (
    <>
      {/* オーバーレイ */}
      <motion.div
        className="fixed inset-0 bg-black/50"
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        onClick={onClose}
      />

      {/* パネル — アクセシビリティ要件: フォーカストラップ、Escapeキーで閉じる、
          スクロールロック、role="dialog"、aria-modal="true" */}
      <motion.div
        role="dialog"
        aria-modal="true"
        className="fixed inset-x-4 top-1/2 -translate-y-1/2 rounded-xl bg-white p-6"
        initial={{
          opacity: 0,
          scale: motionTokens.scale.press,
          y: motionTokens.distance.sm,
        }}
        animate={{ opacity: 1, scale: 1, y: 0 }}
        exit={{
          opacity: 0,
          scale: motionTokens.scale.press,
          y: motionTokens.distance.sm,
        }}
        transition={springs.gentle}
      />
    </>
  )
}

トーストスタック

"use client"
import { motion, AnimatePresence } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"

<AnimatePresence mode="sync">
  {toasts.map((t) => (
    <motion.div
      key={t.id}
      layout
      initial={{
        opacity: 0,
        x: motionTokens.distance.xl,
        scale: motionTokens.scale.subtle,
      }}
      animate={{ opacity: 1, x: 0, scale: 1 }}
      exit={{
        opacity: 0,
        x: motionTokens.distance.xl,
        scale: motionTokens.scale.subtle,
      }}
      transition={springs.snappy}
    />
  ))}
</AnimatePresence>

ページ遷移(Next.js App Router)

// components/page-transition.tsx
"use client"
import { motion, AnimatePresence } from "motion/react"
import { usePathname } from "next/navigation"
import { motionTokens } from "@/lib/motion-tokens"

const variants = {
  initial: { opacity: 0, y: motionTokens.distance.sm },
  enter:   { opacity: 1, y: 0 },
  exit:    { opacity: 0, y: -motionTokens.distance.sm },
}

export function PageTransition({ children }: { children: React.ReactNode }) {
  const pathname = usePathname()
  return (
    <AnimatePresence mode="wait">
      <motion.div
        key={pathname}
        variants={variants}
        initial="initial"
        animate="enter"
        exit="exit"
        transition={{
          duration: motionTokens.duration.normal,
          ease: motionTokens.easing.smooth,
        }}
      >
        {children}
      </motion.div>
    </AnimatePresence>
  )
}

スクロールリビール

"use client"
import { motion } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"

<motion.div
  initial={{ opacity: 0, y: motionTokens.distance.lg }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: "-80px" }}   // once: true — ルール7
  transition={{ duration: motionTokens.duration.slow, ease: motionTokens.easing.smooth }}
/>

スクロールプログレスバー

"use client"
import { motion, useScroll } from "motion/react"

export function ScrollProgress() {
  const { scrollYProgress } = useScroll()
  return (
    <motion.div
      className="fixed top-0 left-0 h-1 bg-indigo-500 origin-left w-full"
      style={{ scaleX: scrollYProgress }}
    />
  )
}

展開カード

"use client"
import { useState } from "react"
import { motion, AnimatePresence } from "motion/react"
import { springs, motionTokens } from "@/lib/motion-tokens"

export function ExpandingCard({ title, body }: { title: string; body: string }) {
  const [expanded, setExpanded] = useState(false)

  return (
    <motion.div layout onClick={() => setExpanded(!expanded)} className="cursor-pointer">
      {/* layout="position" はテキストのリフローがアニメーション化されるのを防ぎます */}
      <motion.h2 layout="position" className="font-semibold">
        {title}
      </motion.h2>

      <AnimatePresence>
        {expanded && (
          <motion.p
            key="body"
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            transition={{ duration: motionTokens.duration.fast }}
          >
            {body}
          </motion.p>
        )}
      </AnimatePresence>
    </motion.div>
  )
}

共有要素クロスフェード

// ソースコンテキスト
<motion.img layoutId="hero-image" src={src} className="w-16 h-16 rounded" />

// 宛先コンテキスト(同じlayoutId — motionが遷移を処理します)
<motion.img layoutId="hero-image" src={src} className="w-full rounded-xl" />

アコーディオン

<motion.div
  initial={false}
  animate={{ opacity: open ? 1 : 0, scaleY: open ? 1 : 0 }}
  style={{ transformOrigin: "top", overflow: "hidden" }}
  transition={{
    duration: motionTokens.duration.normal,
    ease: motionTokens.easing.smooth,
  }}
> {children}
</motion.div>

エンドツーエンドの例

マウント時に登場するスタガーリストで、条件付きプレゼンスを処理し、 動きの軽減を尊重する例。トークン、スプリング、AnimatePresence、そして motion-foundations のアクセシビリティフックを組み合わせています:

"use client"
import { useState } from "react"
import { motion, AnimatePresence } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"
import { useSafeMotion } from "@/hooks/use-reduced-motion"

const containerVariants = {
  hidden: {},
  visible: {
    transition: { staggerChildren: 0.08, delayChildren: 0.1 },
  },
}

function ListItem({ label, onRemove }: { label: string; onRemove: () => void }) {
  const safe = useSafeMotion(motionTokens.distance.sm)
  return (
    <motion.li
      variants={{
        hidden:  safe.initial,
        visible: safe.animate,
      }}
      exit={safe.exit}
      transition={springs.gentle}
      className="flex items-center justify-between p-3 rounded-lg bg-white shadow-sm"
    >
      <span>{label}</span>
      <button onClick={onRemove}>削除</button>
    </motion.li>
  )
}

export function AnimatedList({ items, onRemove }: {
  items: { id: string; label: string }[]
  onRemove: (id: string) => void
}) {
  return (
    <motion.ul
      variants={containerVariants}
      initial="hidden"
      animate="visible"
      className="space-y-2"
    >
      <AnimatePresence mode="popLayout">
        {items.map((item) => (
          <ListItem
            key={item.id}
            label={item.label}
            onRemove={() => onRemove(item.id)}
          />
        ))}
      </AnimatePresence>
    </motion.ul>
  )
}

制約 / 非対象

このスキルでは以下はカバーしません:

  • トークンとスプリングの定義 → motion-foundations を参照
  • ドラッグ操作、スワイプジェスチャー、並べ替え可能リスト → motion-advanced を参照
  • テキストアニメーション(単語/文字のリビール、カウンター) → motion-advanced を参照
  • SVG パスの描画またはモーフィング → motion-advanced を参照
  • カスタムアニメーションフック → motion-advanced を参照
  • motion/react を使用しないCSSのみのトランジション

アンチパターン

アンチパターン違反したルール修正方法
AnimatePresence の子要素に key がないルール1直接の子要素に安定した key を追加する
initial + animate に exit がないルール2常に3つすべてを一緒に定義する
mode="wait" なしのページ遷移ルール3AnimatePresence に mode="wait" を追加する
50項目のリストに layout を使用ルール4mode="popLayout" または明示的なトランスフォームを使用する
10項目のリストで staggerChildren: 0.2ルール50.08–0.10 に制限する
フォーカストラップなしのモーダルルール6focus-trap-react または Radix Dialog を追加する
viewport={{ once: true }} なしの whileInViewルール7繰り返しの登場は気が散り、情報提供にならない
インラインで transition={{ duration: 0.3 }}ルール8motionTokens.duration.normal を使用する

関連スキル

  • motion-foundations — ここにあるすべてのパターンがインポートするトークン、スプリング、useSafeMotion フック、SSRガードを定義します。最初にセットアップする必要があります。
  • motion-advanced — これらのパターンを、ドラッグ、ジェスチャー、SVG、テキスト、カスタムフック、命令型シーケンスで拡張します。このスキルのパターンを再定義することはありません。

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

関連スキル