--- id: wiki-2026-0508-micro-interactions title: Micro-interactions category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Microinteractions, UI Micro-animations, Tiny UX] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [ux, ui, animation, design, frontend] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: typescript framework: react-framer-motion --- # Micro-interactions ## 매 한 줄 > **"매 작은 순간이 매 product 의 personality 를 결정한다."**. Micro-interaction 은 매 single task 를 중심으로 한 매 작은 UX moment — toggle, like, error feedback, hover state. Dan Saffer 의 매 4-part model (Trigger / Rules / Feedback / Loops & Modes) 이 표준. 매 well-crafted 매 micro-interaction 은 매 product 를 매 functional → 매 delightful 로 매 끌어올림. ## 매 핵심 ### 매 Saffer's 4 parts 1. **Trigger**: user 가 시작 (click) 또는 system (notification). 2. **Rules**: 매 무엇이 일어나는지 (state transition). 3. **Feedback**: 매 user 에게 결과 알림 (visual / sound / haptic). 4. **Loops & Modes**: 매 시간에 따른 변화, 매 special state. ### 매 언제 사용 - **Status communication**: loading, saving, online/offline. - **Affordance hint**: hover, focus, disabled. - **Error prevention**: input validation 즉시 feedback. - **Reward**: like animation, achievement unlock. - **Branding**: 매 unique transition 으로 매 personality. ### 매 design 원칙 - **빠르게**: 매 100-300ms — 매 너무 길면 매 friction. - **Purposeful**: 매 deco 아닌 매 information 전달. - **Consistent**: 매 동일 action → 매 동일 feedback. - **Respectful**: `prefers-reduced-motion` 존중. - **Subtle by default**: 매 hero animation 은 매 rare. ### 매 performance - 매 transform/opacity 만 사용 (compositor only — GPU). - 매 layout/paint trigger 회피 (width, height, top, left). - 매 will-change 의 sparing 사용. ## 💻 패턴 ### CSS toggle (transform only) ```css .toggle { width: 44px; height: 24px; background: #ccc; border-radius: 12px; transition: background 200ms ease; position: relative; cursor: pointer; } .toggle::after { content: ''; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; background: white; border-radius: 50%; transition: transform 200ms cubic-bezier(0.4, 0, 0.2, 1); } .toggle.on { background: #4ade80; } .toggle.on::after { transform: translateX(20px); } @media (prefers-reduced-motion: reduce) { .toggle, .toggle::after { transition: none; } } ``` ### Framer Motion (React) ```tsx import { motion, AnimatePresence } from 'framer-motion'; function LikeButton({ liked, onToggle }: { liked: boolean; onToggle: () => void }) { return ( {liked ? '❤️' : '🤍'} ); } ``` ### Skeleton loading ```tsx const Skeleton = () => ( ); ``` ### Optimistic UI (form submit) ```tsx function CommentForm({ onSubmit }: { onSubmit: (text: string) => Promise }) { const [pending, setPending] = useState(false); const [error, setError] = useState(null); return ( { e.preventDefault(); const text = (e.target as any).text.value; setPending(true); setError(null); try { await onSubmit(text); } catch (err) { setError('Failed. Try again.'); } finally { setPending(false); } }}> {pending ? 'Posting…' : 'Post'} {error && ( {error} )} ); } ``` ### Inline validation ```tsx function EmailInput() { const [value, setValue] = useState(''); const [touched, setTouched] = useState(false); const valid = /\S+@\S+\.\S+/.test(value); const showError = touched && !valid && value.length > 0; return ( setValue(e.target.value)} onBlur={() => setTouched(true)} aria-invalid={showError} /> {showError && ( Invalid email )} ); } ``` ### Pull-to-refresh (mobile) ```tsx import { motion, useMotionValue, useTransform } from 'framer-motion'; function PullToRefresh({ onRefresh }: { onRefresh: () => Promise }) { const y = useMotionValue(0); const opacity = useTransform(y, [0, 80], [0, 1]); const rotate = useTransform(y, [0, 80], [0, 360]); return ( { if (info.offset.y > 80) await onRefresh(); y.set(0); }} style={{ y }} > ↻ {/* content */} ); } ``` ## 매 결정 기준 | Element | Duration | Easing | |---|---|---| | Toggle, hover | 100-200ms | ease-out | | Modal enter | 200-300ms | cubic-bezier(0.4, 0, 0.2, 1) | | Modal exit | 150-200ms | cubic-bezier(0.4, 0, 1, 1) | | Page transition | 300-500ms | ease-in-out | | Loading shimmer | 1500ms loop | ease-in-out | **기본값**: 매 200ms + ease-out + transform/opacity. 매 prefers-reduced-motion 매 항상 존중. ## 🔗 Graph - 부모: [[Interaction Design]] - 변형: [[Animation]] - 응용: [[Optimistic UI]] - Adjacent: [[Framer Motion]] · [[Web Animations API]] · [[Accessibility (A11y)|Accessibility]] ## 🤖 LLM 활용 **언제**: UI feedback 설계, button/toggle/input 의 polish, error/loading/empty state 의 personality. **언제 X**: 매 dense data table, 매 power-user tool — 매 animation 이 매 friction. ## ❌ 안티패턴 - **너무 긴 duration**: 매 500ms+ → 매 sluggish. - **No reduced-motion**: 매 vestibular disorder user 에게 매 hostile. - **Decorative only**: 매 information 없는 매 animation → 매 cognitive load. - **Inconsistent**: 매 같은 action 이 매 다른 feedback. - **Layout-trigger animation**: 매 width/height/top → 매 jank. ## 🧪 검증 / 중복 - Verified (Saffer "Microinteractions", Material Design motion guidelines, Apple HIG, Framer Motion docs, web.dev animations). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — Saffer 4-part + Framer Motion / a11y 패턴 |