Animation Guidelines
CSS vs motion animation strategy.
Animation Guidelines
How @ziteox/ui handles motion and when to use each approach.
Library standard
| Library | Status |
|---|---|
motion (motion/react) | Standard — use for complex animation |
framer-motion | Do not add — migrate to motion if importing legacy code |
motion is a peer dependency. Apps install it once.
Decision tree
Does the animation need spring physics, layout animation,
AnimatePresence, or multi-property SVG morph?
├── Yes → use motion
└── No → can CSS transitions handle it?
├── Yes → use CSS (preferred for simplicity)
└── No → use motionCSS transitions
Use for:
- Simple state morphs (hamburger → X)
- Opacity / color hovers
- Single-element transforms
Current example: HamburgerButton
Requirements:
- Drive state via attributes (
aria-expanded) or classes, not inline style animation - Use
usePrefersReducedMotionto shorten or disable transitions - Use
useSkipInitialAnimationto prevent hydration flash
const skipInitialAnimation = useSkipInitialAnimation();
const prefersReducedMotion = usePrefersReducedMotion();motion
Use for:
- Spring-based SVG morphs (
AnimatedThemeToggler) - Enter/exit with
AnimatePresence(futureMobileMenu) - Staggered children
layoutIdshared layout animations
Presets: define springs in animations/springs.ts:
export const iconSpring = {
type: "spring" as const,
stiffness: 380,
damping: 30,
};Do not duplicate spring constants inline in components.
Future sheet/menu springs (not yet in package): add to animations/ when MobileMenu is implemented — do not pre-build unused presets.
Shared hooks (internal)
| Hook | Purpose |
|---|---|
useSkipInitialAnimation | Returns true on first render; disables initial transition |
usePrefersReducedMotion | Reads (prefers-reduced-motion: reduce) |
useMounted | false until after mount; hydration-safe rendering |
Located in packages/ui/src/shared/hooks/. Not exported publicly.
Skip initial animation pattern
const skipInitialAnimation = useSkipInitialAnimation();
const transition = skipInitialAnimation ? noTransition : iconSpring;For CSS components, apply modifier class when skipInitialAnimation is true.
Reduced motion pattern
const prefersReducedMotion = usePrefersReducedMotion();
// motion: use noTransition or duration 0
// CSS: shorter duration class or transition: noneWhen both apply, prefer no animation.
Audio feedback
Procedural Web Audio (see theme/play-tick.ts) is optional per component.
- Colocate in domain folder until second consumer exists
- Fail silently if API unavailable
- Gate with prop (
sound={false})
What to avoid
- Adding animation libraries beyond
motion - Animating layout properties without performance consideration
- Hardcoded
duration: 300in every file — extract or use presets - Relying on parent
groupclass for hover — scope hover on component root - Module-level animation side effects (
sideEffects: falsein package.json)
Component reference
| Component | Animation type | Library |
|---|---|---|
AnimatedThemeToggler | SVG spring morph + button scale | motion |
HamburgerButton | Bar transform morph | CSS |
BrandCredit | Heart color on hover | CSS |
ThemeToggle | Delegates to AnimatedThemeToggler | motion (indirect) |