emilkowal-animations-swift

Installation
SKILL.md

SwiftUI & AppKit Animation Best Practices

Comprehensive animation guide for Apple platform interfaces, adapted from Emil Kowalski's web animation principles and Framer Motion best practices. Contains 86 rules across 10 categories targeting iOS 17+, prioritized by impact.

Review Format (Required)

When reviewing UI animation code, you MUST use a markdown table with Before/After/Why columns. Do NOT use a list with "Before:" and "After:" on separate lines. Always output an actual markdown table like this:

Before After Why
.animation(.default) .animation(.easeOut(duration: 0.2)) Specify explicit easing; avoid default
scaleEffect(0) scaleEffect(0.95) with .opacity(0) Nothing in the real world appears from nothing
.easeIn on dropdown .easeOut(duration: 0.2) easeIn feels sluggish; easeOut gives instant feedback
No press feedback on button scaleEffect(isPressed ? 0.97 : 1.0) Buttons must feel responsive to press
.scaleEffect(anchor: .center) on popover .scaleEffect(anchor: .top) matching trigger Popovers should scale from their trigger, not center

One row per issue found. The "Why" column briefly explains the reasoning.

Review Checklist

When reviewing SwiftUI animation code, check for these issues:

Issue Fix
.animation(.default) or no explicit curve Specify exact easing: .easeOut(duration: 0.2) or .spring(duration: 0.3)
scaleEffect(0) entry animation Start from scaleEffect(0.95) with .opacity(0)
.easeIn on entering UI element Switch to .easeOut or .spring()
.scaleEffect(anchor: .center) on popover Set anchor to match trigger location (modals are exempt)
Animation on keyboard-initiated action Remove animation entirely — use instant state change
Duration > 300ms on UI element Reduce to 150-250ms or use spring
Hover animation without #if os(macOS) guard Gate behind platform check or .onHover availability
withAnimation wrapping high-frequency action Remove — actions triggered 100+/day should be instant
Animating .frame() or .padding() Use .scaleEffect, .offset, .opacity instead (GPU-accelerated)
Same enter/exit transition speed Make exit faster: .asymmetric(insertion: 0.3s, removal: 0.15s)
Elements all appear at once Add stagger delay (30-80ms between items)
Manual Timer/CADisplayLink animation loop Use .animation() or withAnimation (runs on render server)
Missing accessibilityReduceMotion check Add @Environment(\.accessibilityReduceMotion) guard
No press feedback on tappable element Add scaleEffect(0.97) on press via ButtonStyle

When to Apply

Reference these guidelines when:

  • Adding animations to SwiftUI views
  • Choosing easing curves, springs, or timing values
  • Implementing gesture-based interactions (drag, tap, long press)
  • Building transitions and navigation animations
  • Using matchedGeometryEffect for shared element transitions
  • Creating scroll-linked or parallax effects
  • Using iOS 17+ PhaseAnimator or KeyframeAnimator
  • Optimizing animation performance
  • Ensuring animation accessibility with accessibilityReduceMotion
  • Writing AppKit/macOS-specific animations

Rule Categories by Priority

Priority Category Impact Prefix
1 Timing Curves & Easing CRITICAL ease-
2 Duration & Timing CRITICAL timing-
3 Animation Properties HIGH props-
4 Transforms & Effects HIGH transform-
5 Gesture & Interaction HIGH gesture-
6 Transitions & Navigation MEDIUM-HIGH transition-
7 Scroll & Parallax MEDIUM scroll-
8 Strategic Animation MEDIUM strategy-
9 Accessibility & Polish MEDIUM polish-
10 AppKit Specific LOW-MEDIUM appkit-

Quick Reference

1. Timing Curves & Easing (CRITICAL)

2. Duration & Timing (CRITICAL)

3. Animation Properties (HIGH)

4. Transforms & Effects (HIGH)

5. Gesture & Interaction (HIGH)

6. Transitions & Navigation (MEDIUM-HIGH)

7. Scroll & Parallax (MEDIUM)

8. Strategic Animation (MEDIUM)

9. Accessibility & Polish (MEDIUM)

10. AppKit Specific (LOW-MEDIUM)

Key Values Reference

Value Usage
.spring(duration: 0.3, bounce: 0.2) Standard iOS 17 spring animation
.spring(response: 0.3, dampingFraction: 0.7) Classic spring configuration
.easeOut(duration: 0.2) Standard UI transition
scaleEffect(0.97) Button press feedback
scaleEffect(0.95) Minimum enter scale (never scale to 0)
0.2 seconds Default micro-interaction duration
0.3 seconds Maximum duration for UI animations
0.5 seconds Sheet/drawer animation duration

Concept Mapping (Web to SwiftUI)

Web Concept SwiftUI Equivalent
ease-out .easeOut or Animation.easeOut(duration:)
cubic-bezier(a,b,c,d) .timingCurve(a, b, c, d, duration:)
Framer Motion spring .spring(duration:bounce:) (iOS 17+)
transform: scale(0.97) .scaleEffect(0.97)
transform-origin .scaleEffect(anchor: .topLeading)
useMotionValue @State / @GestureState
AnimatePresence .transition() + .id()
layoutId .matchedGeometryEffect(id:in:)
useScroll GeometryReader + PreferenceKey
whileHover/whileTap .onHover {} / gesture modifiers
dragConstraints DragGesture with onChange bounds
prefers-reduced-motion @Environment(\.accessibilityReduceMotion)

Reference Files

File Description
references/_sections.md Category definitions and ordering
assets/templates/_template.md Template for new rules
metadata.json Version and reference information
Installs
39
Repository
pluk-inc/skills
GitHub Stars
1
First Seen
Jan 29, 2026