framer-motion-sticky-scroll-layout
Installation
SKILL.md
Framer Motion Sticky Scroll Section Layout
Problem
When building scroll-driven sections with CSS position: sticky and Framer Motion animated
slides (absolute-positioned children that fade in/out based on scroll progress), the children
render incorrectly — often stacking, overflowing, or appearing at wrong positions. Framer
Motion logs: "Please ensure that the container has a non-static position."
Context / Trigger Conditions
- Building a scroll-pinned section with multiple "slides" that transition as user scrolls
- Using
position: stickywith a tall scroll runway (e.g.,height: 300vh) - Child slides use
position: absolute; inset: 0to stack on top of each other - Framer Motion
animatetogglesopacityandybased onuseScroll+useTransform - Console warning about non-static position on the container
Solution
Correct structure:
<div ref={scrollRef} className="relative" style={{ height: '300vh' }}>
{/* Sticky viewport */}
<div className="sticky top-0 h-screen overflow-hidden">
{/* CRITICAL: This relative wrapper is the positioning context */}
<div className="relative w-full h-full">
{/* Slide 1 */}
<motion.div
className="absolute inset-0 flex items-center"
animate={{ opacity: currentSlide === 0 ? 1 : 0 }}
>
<div className="max-w-6xl mx-auto w-full px-8">
{/* Content */}
</div>
</motion.div>
{/* Slide 2 */}
<motion.div
className="absolute inset-0 flex items-center justify-center"
animate={{ opacity: currentSlide === 1 ? 1 : 0 }}
>
{/* Content */}
</motion.div>
</div>
</div>
</div>
Key rules:
- The
relative w-full h-fulldiv MUST be a direct child of the sticky container - All slides use
absolute inset-0to fill this relative container - Content padding (
px-8,max-w-6xl) goes INSIDE each slide, not on the container - Use
flex items-centeron each absolute slide for vertical centering - Each slide gets its own layout (grid, flex, centered) — they don't share a parent layout
Common mistake — putting content wrapper between sticky and slides:
// WRONG: max-w-6xl wrapper between sticky and absolute slides
<div className="sticky top-0 h-screen">
<div className="max-w-6xl mx-auto w-full px-8">
<motion.div style={{ position: 'absolute', inset: 0 }}>
{/* This breaks because absolute child escapes the static parent */}
</motion.div>
</div>
</div>
Verification
- Console warning about non-static position disappears
- All slides render centered vertically in the viewport
- Slides transition smoothly as scroll progresses through the runway
- Content has proper padding from viewport edges on all slides
Notes
- The
overflow-hiddenon the sticky div prevents scroll-driven content from bleeding - For
useScrollwithtarget, the ref goes on the outer runway div, not the sticky div - Map scroll progress to slide index:
useTransform(scrollYProgress, [0, 0.33, 0.34, 0.66, 0.67, 1], [0, 0, 1, 1, 2, 2]) - On mobile, skip sticky entirely — use a simple stacked layout with
whileInViewanimations instead - Use
useState+useEffectwithmotionValue.on('change')to convert Framer Motion values to React state for conditional rendering