Tips & Tricks (업데이트: 2026. 6. 2.)

Claude Code와 Framer Motion: Motion for React 실전 가이드

Claude Code로 Framer Motion/Motion for React를 구현하는 방법. 프롬프트, 실행 가능한 코드, 함정, 접근성, 검증까지 정리합니다.

Claude Code와 Framer Motion: Motion for React 실전 가이드

많은 개발자는 여전히 Framer Motion이라는 이름으로 검색하지만, 2026년 기준 공식 React 문서는 이 라이브러리를 Motion for React로 안내합니다. 새 구현에서는 motion 패키지를 설치하고 React API를 motion/react에서 가져오는 방식을 기준으로 잡는 것이 좋습니다. 기존 프로젝트가 이미 framer-motion으로 통일되어 있다면 갑자기 섞지 말아야 하지만, 신규 코드나 문서 업데이트라면 공식 Motion for React 문서를 기준으로 삼아야 합니다.

Claude Code는 애니메이션 작업을 빠르게 만들 수 있지만, 제품 판단까지 자동으로 대신하지는 않습니다. “자연스럽게 움직이게 해줘”라고만 지시하면 과한 튕김, Reduced Motion 미대응, 부모 컴포넌트가 먼저 사라져 exit가 실행되지 않는 문제가 쉽게 생깁니다. Masa가 내부 대시보드에서 실험했을 때도 첫 결과물은 보기엔 화려했지만 카드가 너무 많이 움직였고, 알림은 종료 애니메이션 없이 사라졌으며, 스크롤 헤더는 저사양 노트북에서 무거웠습니다.

이 글은 Claude Code를 막연한 코드 생성기가 아니라 제약을 공유하는 협업자로 다룹니다. 먼저 목적, 범위, 접근성 조건, 검증 방식을 정하고 나서 구현을 맡기는 흐름입니다. 아래 TSX 예시는 npm install motion 후 React 프로젝트에 붙여 넣어 실행할 수 있는 수준으로 작성했습니다. Next.js나 Astro에서는 애니메이션 부분을 클라이언트 컴포넌트로 분리하세요.

설계 제약부터 정하기

애니메이션은 장식이 아니라 상태 변화를 이해시키는 UI입니다. Claude Code에 맡기기 전에 네 가지를 명확히 적습니다.

항목Claude Code에 전달할 내용리뷰할 점
목적어떤 상태 변화를 설명할지움직임이 이해를 돕는지
범위수정 가능한 파일과 금지 파일불필요한 리팩터링이 섞였는지
APImotion/react, Reduced Motion 대응import와 동작이 문서와 맞는지
검증수동 조작, 키보드, 저사양 확인실제 사용 흐름으로 테스트했는지

작업 흐름은 다음처럼 잡으면 안정적입니다.

목적과 제약
  -> Claude Code 프롬프트
  -> Motion for React 구현
  -> 수동 및 접근성 검증
  -> 필요한 부분만 재요청

첫 프롬프트는 짧아도 경계를 포함해야 합니다.

기존 React 컴포넌트에 Motion for React 애니메이션을 추가해 주세요.
조건:
- 새 Motion API는 `motion/react`에서 import
- 데이터 fetch, form submit, routing 로직은 변경 금지
- Reduced Motion을 지원
- opacity와 transform 중심으로 구현
- layout shift를 만들지 않기
- 변경 후 수동 테스트 체크리스트를 함께 제시

이렇게 쓰면 Claude Code가 리뷰 가능한 diff를 만들 가능성이 높아집니다. 특히 import 출처는 꼭 명시하세요. 오래된 예제가 많아서 framer-motionmotion/react가 한 파일에 섞이는 일이 생기기 쉽습니다.

사용 사례1: 카드 목록 입장 애니메이션

대시보드, 글 목록, 기능 카드에는 조용한 stagger가 잘 맞습니다. 목표는 카드가 하나씩 공연하는 것이 아니라 읽는 순서를 자연스럽게 만드는 것입니다. Masa의 검증에서는 카드 6개 기준 0.06초에서 0.09초 정도가 실용적이었고, 그보다 길면 조작 시작이 늦어졌습니다.

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

type Feature = {
  id: string;
  title: string;
  body: string;
  metric: string;
};

const demoFeatures: Feature[] = [
  {
    id: "review",
    title: "리뷰 대기",
    body: "Claude Code가 만든 diff를 사람이 확인하는 큐입니다.",
    metric: "8개",
  },
  {
    id: "motion",
    title: "모션 개선",
    body: "짧은 전환으로 화면 변화를 설명하고 작업 속도는 유지합니다.",
    metric: "14%",
  },
  {
    id: "a11y",
    title: "Reduced Motion",
    body: "움직임을 줄이고 싶은 사용자에게는 부드러운 fade를 제공합니다.",
    metric: "완료",
  },
];

export function AnimatedFeatureCards({
  items = demoFeatures,
}: {
  items?: Feature[];
}) {
  const shouldReduceMotion = useReducedMotion();

  const container = {
    hidden: { opacity: 0 },
    visible: {
      opacity: 1,
      transition: {
        staggerChildren: shouldReduceMotion ? 0 : 0.08,
      },
    },
  };

  const card = {
    hidden: {
      opacity: 0,
      y: shouldReduceMotion ? 0 : 16,
    },
    visible: {
      opacity: 1,
      y: 0,
      transition: {
        duration: 0.32,
        ease: "easeOut",
      },
    },
  };

  return (
    <motion.section
      aria-label="기능 카드"
      variants={container}
      initial="hidden"
      animate="visible"
      style={{
        display: "grid",
        gap: 16,
        gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
      }}
    >
      {items.map((item) => (
        <motion.article
          key={item.id}
          variants={card}
          whileHover={shouldReduceMotion ? undefined : { y: -4 }}
          style={{
            border: "1px solid #d9e2ec",
            borderRadius: 12,
            padding: 20,
            background: "#ffffff",
            boxShadow: "0 8px 24px rgba(15, 23, 42, 0.08)",
          }}
        >
          <p style={{ margin: 0, color: "#2563eb", fontWeight: 700 }}>
            {item.metric}
          </p>
          <h3 style={{ margin: "8px 0", fontSize: 18 }}>{item.title}</h3>
          <p style={{ margin: 0, lineHeight: 1.7, color: "#475569" }}>
            {item.body}
          </p>
        </motion.article>
      ))}
    </motion.section>
  );
}

리뷰에서는 stable key, transform 중심의 이동, Reduced Motion일 때 큰 움직임이 사라지는지를 봅니다. 후속 프롬프트는 “카드가 12개일 때도 즉시 조작할 수 있도록 stagger를 조정해 주세요”처럼 구체적으로 주는 편이 좋습니다.

사용 사례2: 알림 추가와 삭제

AnimatePresence는 React 트리에서 제거되는 요소에 종료 애니메이션을 주는 컴포넌트입니다. 공식 AnimatePresence 문서처럼 직접 자식에는 안정적인 key가 필요합니다. 자주 생기는 실패는 알림 영역 전체를 조건부 렌더링해서 부모가 먼저 사라지는 경우입니다.

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

type Toast = {
  id: number;
  message: string;
};

let nextToastId = 1;

export function AnimatedNotifications() {
  const [toasts, setToasts] = useState<Toast[]>([
    { id: 0, message: "저장했습니다" },
  ]);

  function addToast() {
    const id = nextToastId++;
    setToasts((current) => [
      ...current,
      { id, message: `백그라운드 작업 ${id} 완료` },
    ]);
  }

  function dismissToast(id: number) {
    setToasts((current) => current.filter((toast) => toast.id !== id));
  }

  return (
    <div>
      <button type="button" onClick={addToast}>
        알림 추가
      </button>

      <div
        aria-live="polite"
        style={{
          position: "fixed",
          right: 24,
          top: 24,
          display: "grid",
          gap: 12,
          width: "min(360px, calc(100vw - 48px))",
        }}
      >
        <AnimatePresence initial={false} mode="popLayout">
          {toasts.map((toast) => (
            <motion.div
              layout
              key={toast.id}
              initial={{ opacity: 0, x: 40, scale: 0.96 }}
              animate={{ opacity: 1, x: 0, scale: 1 }}
              exit={{ opacity: 0, x: 40, scale: 0.96 }}
              transition={{ duration: 0.2, ease: "easeOut" }}
              style={{
                borderRadius: 10,
                border: "1px solid #cbd5e1",
                background: "#ffffff",
                padding: 16,
                boxShadow: "0 12px 30px rgba(15, 23, 42, 0.16)",
              }}
            >
              <p style={{ margin: "0 0 12px", color: "#0f172a" }}>
                {toast.message}
              </p>
              <button type="button" onClick={() => dismissToast(toast.id)}>
                닫기
              </button>
            </motion.div>
          ))}
        </AnimatePresence>
      </div>
    </div>
  );
}

추가로 확인할 점은 알림이 0개여도 컨테이너가 유지되는지, 키보드로 닫을 수 있는지, aria-live가 너무 많은 문장을 읽지 않는지입니다. 알림은 반복해서 보는 UI이므로 화려함보다 조용한 사용성이 중요합니다.

사용 사례3: 긴 페이지의 스크롤 진행률

문서, 온보딩, 랜딩 페이지에서는 현재 위치를 알려주는 진행 표시가 유용합니다. Motion의 useScroll은 페이지나 특정 요소의 스크롤 진행률을 MotionValue로 반환합니다. useSpring과 조합하면 흔들림이 적은 progress bar를 만들 수 있습니다.

import { useRef } from "react";
import {
  motion,
  useReducedMotion,
  useScroll,
  useSpring,
  useTransform,
} from "motion/react";

const sections = [
  {
    title: "요구사항 고정",
    body: "Claude Code가 수정하기 전에 목적, 대상 파일, 금지 사항을 적습니다.",
  },
  {
    title: "작게 움직이기",
    body: "먼저 opacity와 transform으로 이해를 돕는 움직임만 만듭니다.",
  },
  {
    title: "실제 경로 검증",
    body: "공개 전 저사양 기기, 키보드 조작, Reduced Motion을 확인합니다.",
  },
];

export function ScrollReadingProgress() {
  const articleRef = useRef<HTMLElement | null>(null);
  const shouldReduceMotion = useReducedMotion();
  const { scrollYProgress } = useScroll({
    target: articleRef,
    offset: ["start start", "end end"],
  });

  const scaleX = useSpring(scrollYProgress, {
    stiffness: 120,
    damping: 28,
    mass: 0.2,
  });
  const y = useTransform(scrollYProgress, [0, 1], [0, -48]);

  return (
    <article ref={articleRef} style={{ position: "relative", padding: 24 }}>
      <motion.div
        aria-hidden="true"
        style={{
          position: "sticky",
          top: 0,
          zIndex: 10,
          height: 4,
          scaleX: shouldReduceMotion ? 1 : scaleX,
          transformOrigin: "0% 50%",
          background: "#2563eb",
        }}
      />

      <motion.header
        style={{
          y: shouldReduceMotion ? 0 : y,
          padding: "56px 0 32px",
        }}
      >
        <p style={{ color: "#2563eb", fontWeight: 700 }}>
          Claude Code x Motion
        </p>
        <h2 style={{ fontSize: 36, margin: 0 }}>
          읽는 동안 문맥이 드러나는 페이지
        </h2>
      </motion.header>

      <div style={{ display: "grid", gap: 24 }}>
        {sections.map((section) => (
          <section
            key={section.title}
            style={{
              border: "1px solid #dbe4ee",
              borderRadius: 12,
              padding: 24,
              background: "#ffffff",
            }}
          >
            <h3>{section.title}</h3>
            <p style={{ lineHeight: 1.8 }}>{section.body}</p>
          </section>
        ))}
      </div>
    </article>
  );
}

스크롤 연출은 위치 안내를 위한 보조 장치로만 쓰세요. 모든 섹션을 옆에서 날아오게 만들면 처음에는 재미있어도 다시 읽을 때 방해가 됩니다. Reduced Motion이 켜져 있다면 큰 이동과 시차 효과를 제거하는 것이 안전합니다.

Claude Code에 리뷰까지 맡기기

구현 후에는 다음처럼 실패 모드를 지정해 리뷰를 요청합니다.

현재 diff를 Motion for React 구현으로 리뷰해 주세요.
특히 다음을 엄격히 확인해 주세요.
- `motion/react`와 오래된 `framer-motion` import가 섞이지 않았는지
- `AnimatePresence`의 직접 자식에 안정적인 key가 있는지
- Reduced Motion에서 큰 이동, parallax, 자동 재생식 효과가 꺼지는지
- width, height, top, left를 자주 애니메이션하지 않는지
- 수동 테스트 단계가 실제 사용자 조작인지

이 프롬프트를 추가하면 Claude Code는 생성기에서 첫 번째 reviewer가 됩니다. 주변 파일까지 읽을 수 있으므로 CSS 충돌, 상태 관리 변경, 테스트 누락도 함께 지적하게 할 수 있습니다.

자주 생기는 함정과 공개 전 점검

첫 번째 함정은 오래된 import입니다. 레거시 프로젝트가 framer-motion을 유지하는 것은 가능하지만, 새 구현은 motion 패키지와 motion/react를 기본으로 합니다. 두 번째는 exit 실패를 easing 문제로 오해하는 것입니다. 실제 원인은 부모가 먼저 사라지거나, key가 index이거나, 요소가 AnimatePresence 안에서 제거되지 않는 경우가 많습니다.

세 번째는 layout을 만능으로 보는 것입니다. 순서 변경과 크기 변화에는 강하지만, 크기가 없는 이미지, 늦게 로드되는 폰트, 갑작스러운 Grid 열 변경은 여전히 흔들림을 만듭니다. 네 번째는 접근성을 마지막에 보는 것입니다. Motion의 접근성 가이드는 Reduced Motion 대응을 설명합니다. 큰 이동, 시차, 자동 재생 같은 효과가 있다면 첫 구현부터 대체 경로를 넣어야 합니다.

공개 전에는 공식 문서와 import가 일치하는지, 모든 움직임에 제품 목적이 있는지, Reduced Motion에서 큰 움직임이 사라지는지, AnimatePresence 자식에 안정적인 key가 있는지, 키보드 조작과 focus가 깨지지 않았는지 확인합니다. 관련 주제는 Claude Code Radix UI, Claude Code shadcn/ui, Claude Code 애니메이션 구현을 함께 보세요. Claude Code 자체는 Anthropic의 Claude Code 문서를 참고하면 됩니다.

정리

Claude Code와 Framer Motion을 함께 쓰는 가치는 단순히 빠르게 움직임을 만드는 데 있지 않습니다. 상태 변화, 삭제, 스크롤, 사용자 설정을 구현과 리뷰 단계에서 동시에 확인할 수 있다는 점이 핵심입니다.

Masa가 실제로 테스트한 결과, 첫 프롬프트에 motion/react, Reduced Motion, 수동 검증 조건을 명시한 것이 가장 효과적이었습니다. 나중에 고치는 것보다 첫 diff에 제약을 넣는 편이 리뷰 시간과 되돌림을 줄입니다. 반복해서 쓸 프롬프트는 무료 치트시트에 정리하고, 여러 화면의 UI를 함께 개선하려면 교육 및 상담에서 설계 리뷰까지 묶어 진행하는 것이 현실적입니다.

#Claude Code #Framer Motion #Motion for React #React #animation #UI/UX
무료

무료 PDF: Claude Code 치트시트

이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.

개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.

Masa

작성자 소개

Masa

Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.