- 01.The setup
- 02.First test: zero layoutId
- 03.Second test: layoutId existed but was invisible
- 04.The lesson
The Code Is the Instruction
I built a skill to make Claude generate animated web narratives with Motion's layoutId morphs. Two test rounds proved that prose warnings against fade transitions did nothing — Claude followed the code examples, which were all fades. The fix was rewriting the examples, not the warnings.
Motion is a great primitive for telling a technical story — pitching an idea, demoing a tool — because Claude is genuinely good at web design, better than it is at directing a screen recording. So I shaped a skill called framer-slides: narrow Motion's enormous API down to the handful of techniques you'd use to author a "video" — 4-7 cinematic scenes with narrative timing and, above all, layoutId transitions, where an element morphs, expands, or shrinks as it moves between scenes. That morph is the whole point. Fades are what you do when you've given up. The skill said exactly that, in several places. Claude ignored it.
The setup
The research surfaced one immediate gotcha: framer-motion was renamed to motion in late 2024, with imports now coming from "motion/react". Every code example had to use the new path. Beyond that, I curated a deliberately small toolkit and excluded the rest — drag gestures, 3D transforms, SVG path animation, scroll-linked values. A small API surface keeps Claude from reaching for shiny, irrelevant techniques.
| Feature | Priority |
|---|---|
layoutId | PRIMARY — elements morph between scenes |
AnimatePresence mode="wait" | Core — scene mount/unmount |
variants + staggerChildren | Core — choreographed reveals |
useAnimate + sequences | Supporting — within-scene timing |
| Spring transitions | Supporting — natural feel |
First test: zero layoutId
I told Claude to demonstrate the skill. It produced six scenes built entirely from useAnimate sequences, springs, and staggers. Zero layoutId. Every transition was an opacity fade — the exact thing the skill warned against.
The diagnosis was easy: the skill said layoutId was important but treated it as one technique among equals, and the code examples were mostly fades. Fades are simpler to implement, so Claude took the path of least resistance. So I made layoutId the hero — mandated 2-3 morphs minimum, named "The Fade Wrapper" as an anti-pattern, marked Morph as the DEFAULT transition and Crossfade as LAST RESORT, and put layoutId requirements at the top of the quality checklist.
Second test: layoutId existed but was invisible
This time the morphs were there in the code — and still didn't show. Every scene was wrapped like this:
<motion.div
key={sceneKey}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.1 }}
>
{/* Scene content with layoutId elements */}
</motion.div>That wrapper is itself the Fade Wrapper anti-pattern — and my own reference code was doing it on every scene. As the wrapper faded to opacity 0 on exit, it dragged the layoutId children down with it, suppressing the morph. Result: flicker, slide-deck feel, morphs technically present but invisible.
The real conflict is architectural. AnimatePresence wants to own enter/exit on the scene wrapper; layoutId wants children to morph freely across scenes. Those two goals fight, and the wrapper wins. The resolution is a per-scene wrapper strategy: a scene whose elements share a layoutId with the next scene uses a plain <div> and lets the morph be the transition; only scenes with no shared elements get a <motion.div> fade, and individual non-shared elements get their own exit props.
So I rewrote all three reference scene patterns to use plain <div> wrappers, with an inline comment in each explaining the choice:
{/* Wrapper: plain <div> because layoutId="title" and layoutId="accent"
connect to the next scene. The morph IS the transition. */}
<div style={{ /* layout styles */ }}>The lesson
Code examples are instructions; prose warnings are suggestions. When the skill said "don't use fade wrappers" and every example used a fade wrapper, Claude followed the code — every time. The fix was never more warnings. It was making the examples obey the rules they were teaching. If a skill has an anti-pattern section, grep your own examples for violations of it, because that's where Claude actually learns the pattern. And "from scratch each time" — no scaffold, no template — sharpens the stakes: the examples are the template. There's no other source of truth, so they have to be right.