New skills (14): - nestjs-best-practices: 40 priority-ranked rules (kadajett) - fastapi: Pydantic v2, async SQLAlchemy, JWT auth (jezweb) - architecture-patterns: Clean Architecture, Hexagonal, DDD (wshobson) - python-performance-optimization: Profiling and optimization (wshobson) - ai-sdk: Vercel AI SDK streaming and agent patterns (vercel) - create-agent: Modular agent architecture with OpenRouter (openrouterteam) - proactive-agent: WAL Protocol, compaction recovery, self-improvement (halthelobster) - brand-guidelines: Brand identity enforcement (anthropics) - ui-animation: Motion design with accessibility (mblode) - marketing-ideas: 139 ideas across 14 categories (coreyhaines31) - pricing-strategy: SaaS pricing and tier design (coreyhaines31) - programmatic-seo: SEO at scale with playbooks (coreyhaines31) - competitor-alternatives: Comparison page architecture (coreyhaines31) - referral-program: Referral and affiliate programs (coreyhaines31) README reorganized by domain: Code Quality, Frontend, Backend, Auth, AI/Agent Building, Marketing, Design, Meta. Mosaic Stack is not limited to coding — the Orchestrator serves coding, business, design, marketing, writing, logistics, and analysis. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.1 KiB
5.1 KiB
UI Animation Examples
Snippets and tips for the core rules in SKILL.md.
Table of contents
- Enter and exit
- Spatial rules and stagger
- Drawer (move easing)
- Hover transitions
- Reduced motion
- Origin-aware animations
- Performance recipes
- Practical tips
Enter and exit
/* Toast.module.css */
.toast {
transform: translate3d(0, 6px, 0);
opacity: 0;
transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1),
opacity 220ms cubic-bezier(0.22, 1, 0.36, 1);
}
.toast[data-open="true"] {
transform: translate3d(0, 0, 0);
opacity: 1;
}
/* Disable transitions during theme switch */
[data-theme-switching="true"] * {
transition: none !important;
}
// app/components/Panel.tsx
"use client";
import { motion, useReducedMotion } from "framer-motion";
export function Panel() {
const reduceMotion = useReducedMotion();
return (
<motion.div
whileHover={reduceMotion ? undefined : { scale: 1.02 }}
whileTap={reduceMotion ? undefined : { scale: 0.98 }}
transition={
reduceMotion ? { duration: 0 } : { duration: 0.2, ease: [0.22, 1, 0.36, 1] }
}
/>
);
}
Spatial rules and stagger
/* Menu.module.css */
.menu {
transform-origin: top right;
transform: scale(0.88);
opacity: 0;
transition: transform 200ms cubic-bezier(0.22, 1, 0.36, 1),
opacity 200ms cubic-bezier(0.22, 1, 0.36, 1);
}
.menu[data-open="true"] {
transform: scale(1);
opacity: 1;
}
.list > * {
animation: fade-in 220ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
.list > *:nth-child(2) { animation-delay: 50ms; }
.list > *:nth-child(3) { animation-delay: 100ms; }
const listVariants = {
show: { transition: { staggerChildren: 0.05 } },
};
Drawer (move easing)
.drawer {
transition: transform 240ms cubic-bezier(0.25, 1, 0.5, 1);
}
<motion.aside
initial={{ transform: "translate3d(100%, 0, 0)" }}
animate={{ transform: "translate3d(0, 0, 0)" }}
exit={{ transform: "translate3d(100%, 0, 0)" }}
transition={{ duration: 0.24, ease: [0.25, 1, 0.5, 1] }}
/>
Hover transitions
/* Link.module.css */
@media (hover: hover) and (pointer: fine) {
.link {
transition: color 200ms ease, opacity 200ms ease;
}
.link:hover {
opacity: 0.8;
}
}
Reduced motion
@media (prefers-reduced-motion: reduce) {
.menu,
.toast {
transform: none;
transition: none;
}
}
"use client";
import { motion, useReducedMotion } from "framer-motion";
export function AnimatedCard() {
const reduceMotion = useReducedMotion();
return (
<motion.div
animate={reduceMotion ? { opacity: 1 } : { opacity: 1, scale: 1 }}
initial={reduceMotion ? { opacity: 1 } : { opacity: 0, scale: 0.98 }}
/>
);
}
Origin-aware animations
.popover[data-side="top"] { transform-origin: bottom center; }
.popover[data-side="bottom"] { transform-origin: top center; }
.popover[data-side="left"] { transform-origin: center right; }
.popover[data-side="right"] { transform-origin: center left; }
Performance recipes
Pause looping animations off-screen
// app/hooks/usePauseOffscreen.ts
"use client";
import { useEffect, useRef } from "react";
export function usePauseOffscreen<T extends HTMLElement>() {
const ref = useRef<T | null>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver(([entry]) => {
el.style.animationPlayState = entry.isIntersecting ? "running" : "paused";
});
io.observe(el);
return () => io.disconnect();
}, []);
return ref;
}
Toggle will-change during animation
.animating {
will-change: transform, opacity;
}
Spring defaults (framer-motion)
<motion.div
animate={{ transform: "translate3d(0, 0, 0)" }}
transition={{ type: "spring", stiffness: 500, damping: 40 }}
/>
Practical tips
Record your animations
When something feels off, record the animation and play it back frame by frame.
Fix shaky 1px shifts
Elements can shift by 1px at the start/end of CSS transforms due to GPU/CPU handoff. Apply will-change: transform during the animation (not permanently) to keep compositing on the GPU.
Scale buttons on press
button:active {
transform: scale(0.97);
opacity: 0.9;
}
Avoid animating from scale(0)
.element {
transform: scale(0.95);
opacity: 0;
}
.element.visible {
transform: scale(1);
opacity: 1;
}
Skip animation on subsequent tooltips
.tooltip {
transition:
transform 125ms ease-out,
opacity 125ms ease-out;
transform-origin: var(--transform-origin);
}
.tooltip[data-starting-style],
.tooltip[data-ending-style] {
opacity: 0;
transform: scale(0.97);
}
.tooltip[data-instant] {
transition-duration: 0ms;
}
Fix hover flicker
Apply the hover effect on a parent, animate the child:
.box:hover .box-inner {
transform: translateY(-20%);
}
.box-inner {
transition: transform 200ms ease;
}