Skills
Skill 8 of 18
Coordinate several elements that animate together — decide what moves, in what order, how much they overlap, and what stays still — so a group reads as one intentional motion…
10 minutes · 2,229 words · 22 sections
Install
npx skills add emilkowalski/skills --skill choreographnpx skills add emilkowalski/skillsThe first command installs just this skill, by the name in its SKILL.md; the second installs the whole repository.
When this skill is first invoked without a specific question, respond only with:
I’m ready to choreograph your animations, my knowledge comes from Emil Kowalski’s animation philosophy.
Do not provide any other information until the user asks a question.
This skill handles the relationships between animations: order, overlap, timing and stillness. A single animation’s easing, duration and origin belong to animate. Whether something should animate at all is find-animation-opportunities or remove-animations. Once two or more things move in response to the same event, the decisions here apply.
Agents choreograph by adding. Every child gets a delay: i * 0.1, the panel slides in and then its items trickle in, and the old view waits for the new one politely. Each piece is smooth on its own. Put together, the user waits for the interface to finish performing before they can use it.
Good choreography is mostly about cutting moving parts and running the remaining ones in parallel. Sequence only when order carries meaning: reading order, cause and effect, or one thing physically arriving before another. When a sequence feels right it reads as one gesture, a wave. When it feels wrong it reads as a queue.
linear easing affects a single element. Vary the timing by importance.List every element that moves in response to the event and give each one a role. Do this before writing any timing:
| Role | What it is | Treatment |
|---|---|---|
| Lead | The one thing the user must notice: the title, the new view, the item being acted on | Moves first, travels the most, stays on screen the longest |
| Support | Secondary content that follows the lead: subtitle, CTAs, the rest of the list | Follows with shorter, smaller motion |
| Decoration | Pills, badges, background accents, dividers | Fades only, no movement. Or nothing at all |
| Static | Everything else, including containers whose content animates | Doesn’t animate |
If more than one element wants to be the lead, the design has a hierarchy problem, and motion can’t solve it. Say so.
Most groups shrink at this step. A side panel with eight menu items becomes one move: the panel slides, and the items are just there.
Any two moving parts relate in one of four ways. Choose one deliberately for each pair:
Do both elements belong to the same visual object or the same moment?
├── Yes → TOGETHER. Same start, same timing family. (Drawer height + its content; a chip leaving + siblings closing the gap.)
└── No → Does the order carry meaning (reading order, cause → effect, one arrives before the other)?
├── No → TOGETHER. Order without meaning is just delay.
└── Yes → Would showing both at once look broken (overlap, passing through, two rich views fighting)?
├── Yes → SEQUENCE. The second starts as the first finishes, or just before it.
└── No → OVERLAP. A stagger: the second starts while the first is still moving.Then a fifth option that isn’t a timing at all:
layoutId animations, for example), animate the parent and let the children come along. Images flying into a trash can drop into it because the container they belong to moves, not because each image was tuned on its own.AnimatePresence mode="popLayout". Without a library, set the exiting element to position: absolute at its current position before animating it out.popLayout again, not the default mode, which renders the two views one after the other.ease-in-out body inside an ease-out container feels out of sync. Use MotionConfig (or one shared CSS custom property) to set the family once.Sequence only when overlap would look wrong:
mode="wait"). If you want a crossfade here anyway, use the duplicate-view technique in RECIPES.md (opens in a new tab).forwards (or both) so they hold their end state.Make the handoff overlap slightly. Starting the second move just before the first ends is what turns a queue into one gesture.
Each supporting animation hooks onto a specific moment of the lead: its start, its impact, or its settle. It doesn’t get a delay that happens to look about right.
times: [0.7, 0.9]), with the most important line starting first and the others ~40ms behind.Fixed timing is wrong when the size of the change varies:
clamp(0.15s, Δheight / 500, 0.27s)). See RECIPES.md.custom on both AnimatePresence and the child. Otherwise the leaving element animates with the old direction.A sequence has more states than a single animation. Test each of these:
await Promise.all(...)), or it fights the reset.useRef boolean avoids re-rendering on every phase.AnimatePresence can leave two children mounted. Check it explicitly, and make sure each child has a stable, unique key.An idle animation that invites interaction (a hand that occasionally taps, an icon that wiggles) waits a few seconds before the first run (~2.5s) and repeats rarely (repeatDelay ~6s). It stops while the user is interacting, and resumes only after the interaction’s reset has finished.
Collapse the choreography; don’t just shorten it. Under prefers-reduced-motion: reduce, drop the delays and the movement, keep the opacity, and let the group appear together. If the sequence is the explanation (a step-by-step illustration), jump between its frames instead of animating between them.
Pick the lightest tool that can express the relationships. Recipes for each are in RECIPES.md (opens in a new tab).
--index custom property per item × a delay variable, on a keyframe with animation-fill-mode: both so delayed items don’t flash their final state first. Best for one-shot entrances.delayChildren: stagger(...) on the parent. stagger takes from and ease, which give you non-uniform delays for free. Use this for groups that enter and exit with React state.useAnimate): use it only when several elements coordinate across several events (hover, click, idle timer) and you need to await completion. It’s more powerful and much harder to maintain, so don’t use it for a simple stagger.file:line.One row per moving element, in start order:
| Element | Role | Starts | Duration | Motion | Relationship |
|---|---|---|---|---|---|
| Title | Lead | 0ms | 600ms | fade + 12px rise | — |
| Subtitle | Support | 80ms | 500ms | fade + 8px rise | Overlaps title |
| CTAs | Support | 140ms | 500ms | fade + 6px rise | Overlaps subtitle |
| Pill | Decoration | 0ms | 400ms | fade only | Together with title |
| Background | Static | — | — | — | — |
List 2–5 elements you deliberately removed motion from, each with its reason (“Menu items: one entrance per container, the panel carries them”).
Say what can’t be judged from code: whether the stagger reads as a wave, whether the crossfade reads as one object. Name the specific moment to scrub on a recording.
Talk in relationships and moments (“the lines land with the click”), not adjectives. When a sequence is fine as it is, say so. When the right choreography is no choreography at all, say that too.
Coordinate several elements that animate together — decide what moves, in what order, how much they overlap, and what stays still — so a group reads as one intentional motion instead of a pile of separate animations. Use when building or fixing a staggered list, a hero entrance, a view swap inside a container, a multi-step flow, an element leaving while its siblings close the gap, a morph between rich states, or any interaction with several moving parts (icon + background + lines, panel + content, items flying into a target). Triggers on — stagger, staggerChildren, delayChildren, animation-delay, sequence, sequencing, orchestration, choreography, cascade, wave, timeline, "one after another", "at the same time", crossfade, popLayout, mode="wait", AnimatePresence mode, hero entrance, list entrance, multi-step, step transition, morph, useAnimate, Promise.all animations, "feels mechanical", "feels robotic", "too much going on", "feels slow to open".
The verbatim description from this skill’s front matter — the string an agent matches on to decide whether to load it.
main, last pushed 8 November 2026.SKILL.md, not by matching a directory convention. One layout observed: skills/*/SKILL.md.h1 and no skipped levels:/emilkowalski/skills.md, and each skill at its own .md URL.1 file · 10 KB
Everything this skill ships beside its prose. All of it is set here, as a subchapter of skill 8.
Everything else published alongside the skill.