Motion Design Principles skill for Codex
UI motion that explains, then gets out of the way. Codex gives every animation a purpose or removes it, then applies a small set of duration and easing tokens scaled by distance, with faster ease-in exits and ease-out entrances. Choreography follows one focal motion with capped staggers and directional meaning, springs handle gestures, only transform and opacity animate, and reduced-motion users get fades that keep the meaning.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- motion-design-principles Installs to ~/.agents/skills/motion-design-principles/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install motion-design-principles The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project motion-design-principles Installs to .agents/skills/motion-design-principles/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/motion-design-principles.zipunzip -o motion-design-principles.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) motion-design-principles PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $motion-design-principles, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Sets the motion language for a product UI - when to animate at all, duration and easing tokens by distance and element size, enter and exit asymmetry, choreography and stagger, spatial continuity between states, spring parameters, and reduced-motion alternatives that keep meaning. Library-agnostic with CSS, Web Animations API and Motion examples. Use when defining motion tokens, reviewing animations that feel slow, janky or random, choreographing page or modal transitions, or making animation accessible.
Use it when
- Defining duration and easing tokens for a product
- Animations feel slow, janky, inconsistent or decorative
- Choreographing modal, page, list or shared-element transitions
- Making motion accessible for reduced-motion users
Use something else when
- Designing small hover, press and feedback states: use micro-interactions
- Writing GSAP timelines and ScrollTrigger code: use gsap-animations
How to check the result
Recordings slowed in the DevTools Animations panel match the motion spec, a 4x CPU-throttled trace shows no long frames, and reduced-motion emulation keeps every state change perceivable.
The mistake it prevents
Using the same slow ease-in-out for everything, so entrances feel sluggish and exits linger when users repeat an action.
Good UI motion explains something: where an element came from, what changed, what to look at next. It is fast enough that nobody waits for it, consistent enough to feel like one product, and optional for people who turn it off. If an animation explains nothing, remove it.
When to animate
| Purpose | Example | Animate? |
|---|---|---|
| Feedback that an action registered | Button press, toggle, checkbox | Yes, 100-150 ms |
| Spatial relationship | Sheet slides from the button that opened it; list item expands into detail | Yes |
| State change that could be missed | Item added to cart, inline save confirmation | Yes, brief |
| Attention to something new | New message badge | Once, never looping |
| Continuity during loading | Skeleton shimmer, progress | Subtle, or static under reduced motion |
| Decoration | Parallax, floating blobs, entrance animation on every scroll section | Rarely; never on task-focused screens |
Duration tokens
| Token | Duration | Use |
|---|---|---|
--dur-instant | 100 ms | Press states, color and opacity changes, small toggles |
--dur-fast | 150-200 ms | Tooltips, dropdowns, small reveals, exits |
--dur-base | 250 ms | Modals, cards expanding, sheets on phones |
--dur-slow | 350-400 ms | Large surfaces moving far, page-level transitions on big screens |
| (never) | > 500 ms | Anything users trigger repeatedly |
Scale duration with distance and size: a 40px move needs less time than a full-screen sheet. Desktop and large screens can use slightly longer durations than phones for the same component because distances are larger; wearables shorter.
Easing tokens
:root {
--ease-out: cubic-bezier(0.2, 0, 0, 1); /* entering, responding: fast start, gentle stop */
--ease-in: cubic-bezier(0.4, 0, 1, 1); /* leaving the screen: gentle start, fast end */
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); /* moving between two on-screen positions */
--ease-emphasis: cubic-bezier(0.05, 0.7, 0.1, 1); /* hero moments, large surfaces arriving */
--dur-instant: 100ms; --dur-fast: 180ms; --dur-base: 250ms; --dur-slow: 380ms;
}
| Movement | Easing |
|---|---|
| Element enters or appears | ease-out (decelerate): arrives quickly, settles |
| Element exits | ease-in (accelerate), and about 20-30% shorter than its entrance |
| Element moves or resizes on screen | ease-in-out |
| Color, opacity only | linear or ease-out; short |
| Physical, interruptible gestures (drag release, sheet fling) | Spring |
Never use linear for movement (feels mechanical) or the CSS keyword ease as a
house default (its fast middle looks generic).
Springs
Use springs where the user’s gesture hands off to animation, or where interruption is common. Describe by feel and map to the library:
| Feel | Motion (type: "spring") | Notes |
|---|---|---|
| Snappy UI, no overshoot | bounce: 0, duration: 0.3 | Default for most UI springs |
| Slight life | bounce: 0.15, duration: 0.35 | Cards, toasts |
| Playful | bounce: 0.3 | Rarely; consumer and celebratory moments |
<motion.div layout transition={{ type: "spring", bounce: 0, duration: 0.3 }} />
CSS can approximate a spring with linear() easing generated from a spring curve; keep
it to decorative cases since it is not interruptible like a JS spring.
Choreography
- One focal motion at a time. When several things change, the most important one moves; others fade or simply appear.
- Stagger lists by 20-40 ms per item and cap the total (for example, only the first 6-8 items stagger, the rest appear with the last one). Total sequence under ~400 ms.
- Direction carries meaning. Forward navigation slides in from the trailing edge, back from the leading edge (mirror in RTL). Sheets come from the edge they live on.
- Continuity. When an element transforms into another (card to detail), animate the
shared element (FLIP,
layoutId, View Transitions) rather than fading out and in. - Exit before enter, or crossfade overlapping by about half; do not run two full sequences back to back.
- Small distances. Entrances move 8-24px, not from off-screen, unless it is a sheet or page.
Performance rules
- Animate
transformandopacity. Animatingwidth,height,top,left,marginorbox-shadowtriggers layout or paint each frame; use FLIP ortransform: scaleand a pseudo-element shadow with opacity instead. will-change: transformonly on elements about to animate, removed afterwards.- Hold 60 fps on a mid-range phone. Profile with the Performance panel (CPU 4x slowdown).
- Never block input for an animation; interactions must stay responsive mid-transition, and a second click should redirect, not queue.
Reduced motion
prefers-reduced-motion: reduce means remove movement, not meaning.
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important; animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important; scroll-behavior: auto !important;
}
.toast { transition: opacity var(--dur-fast) linear !important; } /* keep a gentle fade */
}
const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;
el.animate(reduce ? [{ opacity: 0 }, { opacity: 1 }]
: [{ opacity: 0, transform: "translateY(12px)" }, { opacity: 1, transform: "none" }],
{ duration: reduce ? 120 : 250, easing: "cubic-bezier(0.2, 0, 0, 1)", fill: "both" });
- Replace slides, zooms and parallax with fades or instant changes.
- Stop autoplaying loops; anything moving for more than 5 seconds needs a pause control (WCAG 2.2.2).
- Nothing flashes more than three times per second (WCAG 2.3.1).
- In Motion for React,
<MotionConfig reducedMotion="user">disables transform and layout animations while keeping opacity.
Review workflow
- Inventory every animation:
rg -n "transition|animation|@keyframes|animate\(|motion\.|gsap\." src. - For each, write its purpose in one phrase. No purpose: remove it.
- Replace raw values with duration and easing tokens.
- Check enter/exit asymmetry and direction consistency.
- Check performance (properties animated, frame rate under CPU throttling).
- Check reduced motion keeps meaning.
Gotchas
- Durations feel slower on repeated use. Tune by doing the action ten times in a row.
- Spinners for actions under about 300-400 ms create flicker; delay showing them.
transition: allanimates properties you did not intend (including layout ones) and makes theme switches crawl. List properties explicitly.- Animating from
display: noneneeds@starting-styleandtransition-behavior: allow-discretein CSS, or a JS library; otherwise the entrance never plays. - Exit animations need the element to stay mounted (Motion’s
AnimatePresence, or waiting foranimationend) or they are skipped.
Verification
- Record the interaction (screen recording at 60 fps, or the DevTools Animations panel at 10% speed) and check easing and stagger match the spec.
- Toggle reduced motion in DevTools Rendering; every state change is still perceivable.
- Performance trace with 4x CPU slowdown shows no long frames during transitions.
Output: motion spec
| Interaction | Property | Duration | Easing | Reduced motion |
|---|---|---|---|---|
| Modal open | opacity 0->1, translateY 16->0 | base 250 | ease-out | opacity only, 120 |
| Modal close | opacity, translateY 0->8 | fast 180 | ease-in | instant |
| List insert | height via FLIP, opacity | base 250 | ease-in-out | instant |
Related: small feedback states in $micro-interactions; implementation in
$css-animations, $motion-animations or $view-transitions.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Motion Design Principles"
short_description: "UI motion that explains, then gets out of the way"
default_prompt: "$motion-design-principles Define motion tokens for our design system and replace the random durations" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.