CSS Transitions Implementation Guardrails
Invoke as $animation-design-planner frameworks/css-transitions.
This subskill provides CSS transition and keyframe animation guardrails for step 5 of $animation-design-planner. It is invoked automatically when the parent detects CSS-based animation patterns in the project. All baseline guardrails from the parent (transform/opacity preference, reduced-motion, focus/pointer/scroll/z-index rules) still apply.
Guardrails
Sequencing and orchestration
- Use
transitionfor two-state toggles; use@keyframes animationfor multi-waypoint or looping motion. - Chain sequences via
transitionend/animationend— always filter bypropertyNameoranimationNamebecause shorthand declarations fire one event per sub-property. - Stagger children via
animation-delaycombined with a CSS custom property per child (e.g.--i), not JSsetTimeout. - Class toggle order: apply in the same frame. If phase B depends on phase A completing, chain via the end event — do not assume matching durations.
will-change lifecycle
- Apply
will-changejust before the animation starts and remove it after the animation ends. - Permanent
will-changeforces a compositor layer and creates a new stacking context — document explicitly if intentionally kept. - Never use
will-change: contentsorwill-change: scroll-positionspeculatively.
Forced reflow and paint triggers
- The reflow trap: reading a layout property then writing a style in the same synchronous block forces synchronous layout. Batch reads before writes.
- Void-to-value trick: force a reflow between DOM insertion and class addition with
el.offsetHeightto ensure the browser registers the initial state before animating. - Double-rAF pattern (
requestAnimationFrame(() => requestAnimationFrame(() => { ... }))) for maximum reliability on enter transitions when the single-frame trick is insufficient.
Exit animation patterns
- CSS has no animate-then-remove primitive: add an exit class, listen for the end event, then remove the element from the DOM.
- Race guard — the end event never fires if the property isn't actually transitioning. Always set a
setTimeoutfallback matching the expected duration. - Race guard — shorthand fires per sub-property. Filter by
propertyNameor designate a single canonical property for the event listener. - Use
animation-fill-mode: forwardsto hold the final state while DOM removal runs. Remove the element promptly — do not rely onforwardsindefinitely as it holds the compositor layer.
Proof expectations
transitionendfires exactly once for the intended property on each transition.- Rapid toggle (open/close, show/hide) produces no stuck mid-animation states.
will-changeis removed after completion (verify in DevTools Layers panel).prefers-reduced-motion: reducesuppresses or collapses all motion.- Slow-motion review (set durations to 2–3s) confirms sequencing matches the animation contract.