follow-through
Skill7 starsChanged 10 months agoMITView on GitHub
Disney's principle of secondary motion that continues after primary action stops, plus overlapping action for staggered element timing. Use for natural settling, spring physics, and cascading animations.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- ConnorGriffin/follow-throughtheir own version, first seen Aug 4, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- ConnorGriffin/follow-through★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository87 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- motion-designSkill
- 3d-transformsSkill
- accessibilitySkill
- android-nativeSkill
- animation-recipesSkill
- anticipationSkill
- arc-motionSkill
- attention-choreographySkill
- attentionSkill
- audio-reactiveSkill
- blurSkill
- brand-animationSkill
- cameraSkill
- characterSkill
- choice-resolutionSkill
- cinematicSkill
- code-generationSkill
- colorSkill
- component-animationsSkill
- compositingSkill
- consequenceSkill
- container-transformSkill
- css-advancedSkill
- data-visualizationSkill
- depthSkill
- distortionSkill
- dramaturgySkill
- durationSkill
- easingSkill
- emotional-journeySkill
- emotion-mappingSkill
- emotion-selectionSkill
- empty-statesSkill
- entrance-patternsSkill
- exaggerationSkill
- exit-patternsSkill
- exportSkill
- figma-integrationSkill
- frame-timingSkill
- gesture-motionSkill
- glow-shadowSkill
- intentSkill
- ios-nativeSkill
- loading-statesSkill
- loopingSkill
- lottieSkill
- masksSkill
- microstateSkill
- morphingSkill
- motion-auditSkill
- motion-blurSkill
- motion-principlesSkill
- motion-specsSkill
- narrative-arcSkill
- notificationsSkill
- onboardingSkill
- opacitySkill
- orchestratorSkill
- particlesSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 5 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 60
Follow-through is best achieved with spring physics:
- Distinctive: line 441
Too much overshoot: - Keep under 1.2x for UI
- Distinctive: line 443
Fighting springs: - Don't interrupt spring animations
- Distinctive: line 450
squash-stretch: - Follow-through often includes diminishing squash oscillations
- Distinctive: line 453
arc-motion: - Follow-through motion often continues along arc paths
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Follow-Through & Overlapping Action
Secondary elements continue moving after the primary action stops, creating natural, physics-based settling. Overlapping action staggers multiple elements for fluid choreography.
Core Principles
Follow-Through
Secondary elements lag behind and overshoot before settling.
Primary stops: ################ STOP
Secondary: ####################-><-> SETTLE
^
overshoot + oscillationOverlapping Action
Multiple elements animate with staggered timing, not simultaneously.
All at once: ######## (robotic)
########
########
Overlapping: ######## (natural)
########
########Token Reference
Load from: tokens/follow-through.tokens.json
Follow-Through Configs
| Token | Delay | Overshoot | Settle | Use Case |
tight | 30ms | 1.02 | 100ms | Enterprise, controlled |
loose | 80ms | 1.08 | 200ms | Consumer, friendly |
fluid | 120ms | 1.15 | 300ms | Playful, organic |
heavy | 150ms | 1.05 | 400ms | Weighty, dramatic |
elastic | 60ms | 1.25 | 350ms | Bouncy, rubber-like |
Overlap Offsets
| Token | Offset | Effect |
sequential | 20% | Elements nearly finished before next starts |
staggered | 40% | Standard wave effect |
cascading | 60% | Multiple elements animating at once |
simultaneous | 80% | Near-instant cascade |
Spring Physics
Follow-through is best achieved with spring physics:
Spring Parameters:
- stiffness: How quickly it snaps back (higher = faster)
- damping: How quickly oscillations die (lower = bouncier)
- mass: Weight of the element (higher = slower, heavier)
Configs from tokens:
- tight-spring: stiffness: 400, damping: 30, mass: 1
- standard-spring: stiffness: 200, damping: 20, mass: 1
- bouncy-spring: stiffness: 300, damping: 10, mass: 1
- wobbly-spring: stiffness: 180, damping: 8, mass: 1Implementation Presets
CSS
/* Follow-through with keyframes */
@keyframes follow-through-settle {
0% { transform: translateX(0); }
60% { transform: translateX(100px); }
75% { transform: translateX(108px); } /* Overshoot */
85% { transform: translateX(97px); } /* Undershoot */
95% { transform: translateX(101px); } /* Tiny overshoot */
100% { transform: translateX(100px); }
}
/* Staggered list items */
.list-item {
opacity: 0;
transform: translateY(20px);
animation: slide-in 300ms ease-out forwards;
}
.list-item:nth-child(1) { animation-delay: 0ms; }
.list-item:nth-child(2) { animation-delay: 60ms; }
.list-item:nth-child(3) { animation-delay: 120ms; }
.list-item:nth-child(4) { animation-delay: 180ms; }
@keyframes slide-in {
to {
opacity: 1;
transform: translateY(0);
}
}
/* CSS spring approximation using cubic-bezier */
.spring-settle {
transition: transform 400ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
}GSAP
import { gsap } from 'gsap';
import tokens from '../tokens/follow-through.tokens.json';
// Follow-through with elastic easing
function animateWithFollowThrough(element, target, intensity = 'loose') {
const config = tokens['follow-through'][intensity].$value;
return gsap.to(element, {
...target,
duration: config.settleDuration / 1000 + 0.3,
ease: `elastic.out(1, ${1 - config.overshoot + 0.5})`
});
}
// Staggered list animation
function staggerList(items, intensity = 'staggered') {
const offset = parseFloat(tokens.overlap[intensity].$value.offset) / 100;
const staggerTime = 0.3 * (1 - offset); // Convert offset to stagger delay
return gsap.from(items, {
opacity: 0,
y: 30,
duration: 0.4,
stagger: staggerTime,
ease: 'back.out(1.7)'
});
}
// Primary + secondary element follow-through
function primarySecondaryAnimation(primary, secondary, intensity = 'loose') {
const { delay, overshoot, settleDuration } = tokens['follow-through'][intensity].$value;
const tl = gsap.timeline();
// Primary movement
tl.to(primary, {
x: 200,
duration: 0.3,
ease: 'power2.out'
});
// Secondary follows with delay and overshoot
tl.to(secondary, {
x: 200 * overshoot,
duration: 0.3,
ease: 'power2.out'
}, `<${delay / 1000}`); // Start slightly after primary
// Secondary settles back
tl.to(secondary, {
x: 200,
duration: settleDuration / 1000,
ease: 'elastic.out(1, 0.5)'
});
return tl;
}
// Card with badge follow-through
function cardWithBadge(card, badge) {
const tl = gsap.timeline();
// Card moves
tl.to(card, {
y: -20,
duration: 0.2,
ease: 'power2.out'
});
// Badge follows with lag and wobble
tl.to(badge, {
y: -25, // Overshoots
rotation: 5,
duration: 0.25,
ease: 'power2.out'
}, '<0.05'); // Slight delay
// Badge settles
tl.to(badge, {
y: -20,
rotation: 0,
duration: 0.3,
ease: 'elastic.out(1, 0.4)'
});
return tl;
}Framer Motion
import { motion, useAnimation, useSpring } from 'framer-motion';
import tokens from '../tokens/follow-through.tokens.json';
// Spring-based follow-through
const springConfigs = {
tight: { stiffness: 400, damping: 30, mass: 1 },
loose: { stiffness: 200, damping: 20, mass: 1 },
bouncy: { stiffness: 300, damping: 10, mass: 1 },
wobbly: { stiffness: 180, damping: 8, mass: 1 }
};
export const FollowThroughCard: React.FC<{ children: React.ReactNode }> = ({
children
}) => (
<motion.div
whileHover={{ y: -10 }}
transition={{ type: 'spring', ...springConfigs.loose }}
>
{children}
</motion.div>
);
// Staggered children with overlap
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.08, // 80ms between children
delayChildren: 0.1
}
}
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { type: 'spring', ...springConfigs.loose }
}
};
export const StaggeredList: React.FC<{ items: string[] }> = ({ items }) => (
<motion.ul
variants={containerVariants}
initial="hidden"
animate="visible"
>
{items.map((item, i) => (
<motion.li key={i} variants={itemVariants}>
{item}
</motion.li>
))}
</motion.ul>
);
// Parent-child follow-through
export const ParentChildFollowThrough: React.FC = () => {
const controls = useAnimation();
const handleHover = async () => {
await controls.start('hover');
};
return (
<motion.div
className="parent"
whileHover="hover"
onHoverStart={handleHover}
>
<motion.div
className="child"
variants={{
rest: { y: 0, rotate: 0 },
hover: {
y: -15,
rotate: 5,
transition: {
type: 'spring',
stiffness: 200,
damping: 15,
delay: 0.05 // Follow-through delay
}
}
}}
initial="rest"
/>
</motion.div>
);
};Remotion
import { useCurrentFrame, interpolate, spring } from 'remotion';
import tokens from '../tokens/follow-through.tokens.json';
// Spring-based follow-through
export const useFollowThrough = (
primaryStopFrame: number,
config: 'tight' | 'loose' | 'fluid' = 'loose'
) => {
const frame = useCurrentFrame();
const { delay, overshoot, settleDuration } = tokens['follow-through'][config].$value;
const delayFrames = Math.round((delay / 1000) * 30);
const secondaryFrame = frame - delayFrames;
// Spring physics for settling
const springProgress = spring({
frame: secondaryFrame - primaryStopFrame,
fps: 30,
config: {
damping: 15,
stiffness: 150,
mass: 1,
overshootClamping: false
}
});
return {
progress: springProgress,
overshoot: overshoot,
scale: 1 + (overshoot - 1) * (1 - springProgress),
rotation: interpolate(springProgress, [0, 0.5, 1], [5, -2, 0])
};
};
// Staggered list animation
export const StaggeredList: React.FC<{
startFrame: number;
items: React.ReactNode[];
staggerFrames?: number;
}> = ({ startFrame, items, staggerFrames = 3 }) => {
const frame = useCurrentFrame();
return (
<>
{items.map((item, index) => {
const itemStartFrame = startFrame + index * staggerFrames;
const progress = spring({
frame: frame - itemStartFrame,
fps: 30,
config: { damping: 20, stiffness: 200, mass: 1 }
});
const opacity = interpolate(progress, [0, 1], [0, 1]);
const y = interpolate(progress, [0, 1], [30, 0]);
return (
<div
key={index}
style={{
opacity,
transform: `translateY(${y}px)`
}}
>
{item}
</div>
);
})}
</>
);
};
// Card with secondary element
export const CardWithBadge: React.FC<{
moveFrame: number;
children: React.ReactNode;
}> = ({ moveFrame, children }) => {
const frame = useCurrentFrame();
// Primary card spring
const cardSpring = spring({
frame: frame - moveFrame,
fps: 30,
config: { damping: 20, stiffness: 200 }
});
// Secondary badge spring (delayed)
const badgeSpring = spring({
frame: frame - moveFrame - 2, // 2 frame delay
fps: 30,
config: { damping: 12, stiffness: 180 } // Bouncier
});
const cardY = interpolate(cardSpring, [0, 1], [0, -30]);
const badgeY = interpolate(badgeSpring, [0, 1], [0, -35]); // Overshoots
const badgeRotation = interpolate(badgeSpring, [0, 0.7, 1], [0, 8, 0]);
return (
<div style={{ transform: `translateY(${cardY}px)` }}>
{children}
<div
className="badge"
style={{
transform: `translateY(${badgeY - cardY}px) rotate(${badgeRotation}deg)`
}}
/>
</div>
);
};Secondary Element Hierarchy
| Element Type | Delay | Behavior |
| Badges, icons on cards | 1-2 frames | Tight, minimal lag |
| Labels, text | 2-3 frames | Subtle follow |
| Decorative elements | 3-5 frames | Noticeable lag |
| Hair, cloth, ribbons | 5-10 frames | Pronounced drag |
| Independent elements | Varies | Own physics |
Accessibility
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// Reduced motion: remove stagger and settle
const stagger = prefersReducedMotion ? 0 : 0.08;
const transition = prefersReducedMotion
? { duration: 0.01 }
: { type: 'spring', stiffness: 200, damping: 20 };Common Mistakes
- Too much overshoot - Keep under 1.2x for UI
- Same timing everywhere - Vary stagger by importance
- Fighting springs - Don't interrupt spring animations
- No hierarchy - Primary should clearly lead secondaries
- Over-staggering - Too much delay feels sluggish
Related Skills
- anticipation - The wind-up phase that precedes the motion follow-through settles
- squash-stretch - Follow-through often includes diminishing squash oscillations
- secondary-action - Secondary actions have their own follow-through behaviors
- exaggeration - Controls intensity of overshoot and settle oscillations
- arc-motion - Follow-through motion often continues along arc paths
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

