Loading State Animations
Apply Disney's 12 principles to communicate system activity.
Principle Application
Squash & Stretch: Progress indicators can pulse/breathe to show life. Slight scale oscillation (0.98-1.02).
Anticipation: Show loading state immediately on action. Don't wait for slow response to show spinner.
Staging: Loading indicators appear where content will be. Skeleton screens match final layout.
Straight Ahead vs Pose-to-Pose: Design loading as a sequence: instant indicator → progress → completion → content.
Follow Through & Overlapping: Loading fades as content enters. Overlap the transition by 100ms.
Slow In/Slow Out: Progress bars ease-in-out between known percentages. Indeterminate uses smooth oscillation.
Arcs: Circular spinners follow true circular paths. Avoid jerky rotation.
Secondary Action: Skeleton shimmer + subtle pulse. Multiple signals reinforce "loading."
Timing:
- Show spinner after 200ms delay (avoid flash for fast loads)
- Minimum display: 500ms (prevent jarring flash)
- Skeleton shimmer cycle: 1500-2000ms
Exaggeration: Keep minimal - loading shouldn't distract, just reassure.
Solid Drawing: Skeletons should match content proportions. Wrong shapes break the illusion.
Appeal: Loading should feel optimistic, not tedious. Smooth motion suggests progress.
Timing Recommendations
| Loading Type | Appear Delay | Min Display | Animation Cycle |
|---|---|---|---|
| Spinner | 200ms | 500ms | 700-800ms |
| Progress Bar | 0ms | - | smooth fill |
| Skeleton | 0ms | 500ms | 1500ms shimmer |
| Button Spinner | 0ms | 400ms | 600ms |
| Full Page | 100ms | 800ms | 1000ms |
Implementation Patterns
css/* Skeleton shimmer */ .skeleton { background: linear-gradient( 90deg, #e0e0e0 0%, #f0f0f0 50%, #e0e0e0 100% ); background-size: 200% 100%; animation: shimmer 1500ms ease-in-out infinite; } @keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } /* Spinner with smooth rotation */ .spinner { animation: spin 700ms linear infinite; opacity: 0; animation: fade-in 200ms 200ms ease-out forwards, spin 700ms linear infinite; } /* Progress bar with easing */ .progress-fill { transition: width 300ms cubic-bezier(0.4, 0, 0.2, 1); }
Loading-to-Content Transition
css.content-enter { animation: content-reveal 300ms ease-out forwards; } @keyframes content-reveal { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
Key Rules
- Delay spinner appearance by 200ms to avoid flash
- Keep loading visible minimum 500ms once shown
- Skeleton shapes must match real content dimensions
- Transition smoothly from loading to content - never pop
- Respect
prefers-reduced-motion- show static indicator instead

