motion-design
Skill1 starsChanged 46 days agoMITView on GitHub
Motion design system for Astro + Tailwind v4 + Svelte 5 — animation tokens, component state patterns, scroll reveals, performance rules, and modern CSS 2025/2026 features. Use this skill whenever adding any animation, transition, hover effect, scroll reveal, micro-interaction, or motion to any component or page. Also use when the user mentions "animation", "transition", "hover effect", "motion", "scroll reveal", "fade in", "entrance animation", "interactive feel", or wants a page to feel "more alive", "more polished", or "like Stripe/Apple/Linear/Vercel".
46 other projects use a version of this.
Adopted by
Most recently taken up by
- ryanportfolio/HarnessFirmware.comtheir own version, first seen Nov 8, 2026
- ryanportfolio/Corewise.Academytheir own version, first seen Nov 8, 2026
- ryanportfolio/savetokenstheir own version, first seen Nov 8, 2026
- ryanportfolio/STKtheir own version, first seen Nov 8, 2026
- Mmbugraaslan/motion-plustheir own version, first seen Nov 8, 2026
- zane-tv/holdframetheir own version, first seen Nov 8, 2026
Used with
Projects with it also use addskill, apply-firmware and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
ryanportfolioSkilladdskill
Create, import, update, or install repo or personal skills: runtime ownership, resources, discovery checks.
Also in 6 projects that use this
ryanportfolioSkillapply-firmware
Bring current folder (plain folder, existing repo, old or partial firmware copy) up to full Harness Firmware in place. Mirror external repo → adopt-repo.
Also in 6 projects that use this
- +10 more from ryanportfolio
Other versions46 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 46
- LottieFiles/motion-design-skill★ 1.7ksame name
- Jwuthri/Tracely-ai★ 1.3ksame name
- jwuthri-cloud/Tracely-ai★ 979same name
- nahid-sparktales/agent-dispatcher★ 59same name
- imMamdouhaboammar/motion-graphics-skills★ 50same name
- gvergnaud/bise★ 48same name
- adityareddy-dev/deskstudio★ 43same name
- LobzyJay/motion-design-with-claude-and-codex★ 38same name
- mathruffian-dot/claude-motion-explainer★ 30same name
- felvieira/claude-skills-fv★ 27same name
36 more not listed
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 repository81 other files it gives its agents.
- accessibility-action-planSkill
- accessibility-auditSkill
- accessibility-finding-explainerSkill
- accessibility-report-summarySkill
- agent-config-authoringSkill
- agents-sdkSkill
- ai-ux-patternsSkill
- alt-text-generatorSkill
- anti-ai-copySkill
- aria-usage-reviewSkill
- astro-architectureSkill
- astro-client-scriptsSkill
- astro-component-a11y-reviewSkill
- before-after-accessibility-commsSkill
- bfsg-risk-language-softenerSkill
- biomeSkill
- blog-from-transcriptSkill
- brand-voice-dnaSkill
- case-study-from-auditSkill
- client-email-after-auditSkill
- cloudflare-email-serviceSkill
- cloudflare-one-migrationsSkill
- cloudflare-oneSkill
- cloudflareSkill
- complexity-judgmentSkill
- component-doc-writerSkill
- content-clarity-a11y-checkSkill
- content-qaSkill
- conversation-retrospectiveSkill
- darkmodeSkill
- deep-researchSkill
- design-directionsSkill
- design-systemsSkill
- durable-objectsSkill
- faq-from-issuesSkill
- form-accessibility-reviewSkill
- frontend-designSkill
- handoffSkill
- i18nSkill
- image-to-webpSkill
- keyboard-navigation-reviewSkill
- knowledge-baseSkill
- landing-and-site-builderSkill
- landingpage-from-briefingSkill
- lighthouse-axe-result-explainerSkill
- linkedin-post-from-contentSkill
- llm-councilSkill
- local-business-seoSkill
- mcp-builderSkill
- mdx-contentSkill
- native-html-uiSkill
- newsletter-from-auditSkill
- overlay-tool-risk-explainerSkill
- playwrightSkill
- post-auditSkill
- remotionSkill
- sandbox-migrate-to-nextSkill
- sandbox-nextSkill
- sandbox-sdkSkill
- sandbox-stableSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
11 rules. 0 are common, 4 are distinctive, and 7 are very specific to this project.
0
Common
Most of the 42 edited versions we read keep these.
4
Distinctive
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 47
Use in Tailwind with transition-[duration:var(--duration-fast)] or directly in scoped CSS via var().
- Very specific: line 68
Add will-change: transform, opacity only on elements that are known to animate imminently (e.g., a nav drawer that opens).
- Distinctive: line 92
Every interactive component defines these eight states.
- Very specific: line 291
No JavaScript, no IntersectionObserver.
- Very specific: line 481
Always pair these — define the reduced-motion state first as the default, enhance for motion-safe:
- Distinctive: line 526
Every animation must have a reduced-motion fallback.
- Distinctive: line 550
Do not merely slow down animations for reduced-motion — remove them.
- Very specific: line 558
Never
transition: all: Always name the properties: transition-property: transform, opacity. - Very specific: line 559
Scale on press:
scale(0.96): Never below 0.95 — anything smaller feels exaggerated. - Very specific: line 560
Interruptible vs one-shot: Use CSS transitions for interactive state changes (hover/active/focus) — they can be interrupted mid-flight.
- Distinctive: line 566
Before delivering any animation code, verify each item:
“Common” is measured: kept by at least 6 in 10 of the 42 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Motion Design System
Animation is communication. Every motion in the UI must earn its place by answering: does this help the user understand what just happened, where focus is, or what they can do next? If the answer is no, remove it.
Core principles
- Purpose over decoration — animation guides attention, confirms actions, and reveals hierarchy. It does not add sparkle for its own sake.
- Orientation — motion should reinforce spatial relationships. Things that slide in from the right came from the right. Things that expand grew from a point. Misleading direction confuses.
- Speed feels quality — slow animations feel cheap and in the way. Responsive interfaces feel fast because their transitions are fast.
- Mobile first, touch always — hover effects are an enhancement, not a requirement. Test on touch.
- Accessibility is not optional —
prefers-reduced-motion: reducemust always be handled. Users with vestibular disorders and epilepsy rely on this. No exceptions. - Core Web Vitals — animations must never trigger layout (CLS), delay interactivity (INP), or block paint (LCP). Animate only composited properties.
Animation Design Tokens
Define once in the shared CSS entry (src/styles/app.css or equivalent). Every component imports these — no ad-hoc duration-[237ms] arbitrary values.
@theme {
/* Durations */
--duration-instant: 100ms;
--duration-fast: 150ms;
--duration-normal: 300ms;
--duration-slow: 500ms;
--duration-slower: 700ms;
/* Easing */
--ease-standard: cubic-bezier(0.4, 0, 0.2, 1); /* material standard */
--ease-decelerate: cubic-bezier(0, 0, 0.2, 1); /* entering elements */
--ease-accelerate: cubic-bezier(0.4, 0, 1, 1); /* exiting elements */
--ease-emphasized: cubic-bezier(0.2, 0, 0, 1); /* Apple/Linear feel */
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1); /* spring-like pop */
--ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
/* Stagger delay multiplier — multiply by child index */
--stagger-unit: 50ms;
}Use in Tailwind with transition-[duration:var(--duration-fast)] or directly in scoped CSS via var().
Performance rules
Animate ONLY these properties (GPU-composited, zero layout cost)
| Property | Use case |
transform | movement, scale, rotation, skew |
opacity | fade in/out, reveal |
filter | blur, brightness, drop-shadow |
clip-path | shape reveals (GPU in modern browsers) |
NEVER animate these (trigger layout — kills performance and CLS)
width, height, top, left, right, bottom, margin, padding, border-width, font-size, line-height
will-change guidance
Add will-change: transform, opacity only on elements that are known to animate imminently (e.g., a nav drawer that opens). Remove it after the animation with JavaScript. Applying it broadly wastes GPU memory and can slow the page down.
GPU acceleration
translateZ(0) or translate3d(0,0,0) forces GPU layer promotion. Use sparingly — only when you observe compositor jank on a real device.
Apple / Linear / Stripe motion rules
Study these sites before inventing something new. Their shared rules:
- Small movements — translate by
4px–12pxmax. Large travel distances feel unprofessional. - Scale conservatively —
scale(1.02)for hover lift,scale(1.05)maximum. Never beyond. - No gratuitous rotation — max
3degfor a tilt effect. Zero for most components. - Shadow is subtle — animate shadow size and opacity gently; avoid abrupt shadow jumps.
- Combine opacity + transform — a fade that also moves
8pxupward reads as intentional, not accidental. A raw opacity fade alone looks like a mistake. - Stagger reveals — grid items, nav links, and list items should enter with a
50msoffset per item, not all at once. - Exits are faster than entrances — entering:
300ms ease-decelerate. Exiting:200ms ease-accelerate.
Component animation states
Every interactive component defines these eight states. Define them all, even if some are identity transforms. Consistency means no surprises.
| State | CSS trigger | Typical motion |
| idle | default | static |
| hover | :hover / group-hover | lift, glow, scale |
| focus | :focus-visible | focus ring, no scale |
| active | :active | press-down (scale 0.97) |
| loading | JS class .is-loading | spinner, pulse |
| disabled | [disabled] / .is-disabled | opacity 0.4, no hover |
| enter | @starting-style / JS class | fade-up, scale-in |
| exit | JS class .is-exiting | fade-out, scale-down |
Button patterns
/* Tailwind v4 @utility — define once, use as class */
@utility btn-motion {
transition:
transform var(--duration-fast) var(--ease-standard),
box-shadow var(--duration-fast) var(--ease-standard),
background-color var(--duration-fast) var(--ease-standard);
&:hover:not([disabled]) {
transform: translateY(-2px);
box-shadow: 0 4px 12px oklch(0% 0 0 / 15%);
}
&:active:not([disabled]) {
transform: translateY(0) scale(0.97);
box-shadow: none;
transition-duration: var(--duration-instant);
}
&:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 3px;
}
&[disabled], &.is-disabled {
opacity: 0.4;
cursor: not-allowed;
}
}Loading spinner — avoid layout shift by keeping the button the same size:
<button class="btn-motion relative" class:list={[isLoading && 'is-loading']}>
<span class:list={[isLoading ? 'opacity-0' : 'opacity-100', 'transition-opacity duration-150']}>
{label}
</span>
{isLoading && (
<span class="absolute inset-0 flex items-center justify-center">
<span class="size-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
</span>
)}
</button>Card patterns
@utility card-lift {
transition:
transform var(--duration-normal) var(--ease-emphasized),
box-shadow var(--duration-normal) var(--ease-emphasized);
&:hover {
transform: translateY(-4px);
box-shadow: 0 12px 32px oklch(0% 0 0 / 12%);
}
}
@utility card-glow {
transition: box-shadow var(--duration-normal) var(--ease-standard);
&:hover {
box-shadow: 0 0 0 1px var(--color-primary), 0 8px 24px oklch(from var(--color-primary) l c h / 20%);
}
}Image zoom within card (contain zoom inside card bounds):
<div class="overflow-hidden rounded-xl">
<img class="transition-transform duration-500 ease-[--ease-emphasized] group-hover:scale-[1.04]" … />
</div>Navigation patterns
Dropdown — use @starting-style for enter + transition-behavior: allow-discrete for display toggle:
.dropdown-menu {
display: none;
opacity: 0;
transform: translateY(-6px);
transition:
opacity var(--duration-fast) var(--ease-decelerate),
transform var(--duration-fast) var(--ease-decelerate),
display var(--duration-fast) allow-discrete;
@starting-style {
opacity: 0;
transform: translateY(-6px);
}
}
.dropdown-menu.is-open {
display: block;
opacity: 1;
transform: translateY(0);
}Mobile drawer:
.drawer {
transform: translateX(-100%);
transition: transform var(--duration-slow) var(--ease-emphasized);
&.is-open {
transform: translateX(0);
}
}Overlay backdrop:
.backdrop {
opacity: 0;
transition: opacity var(--duration-normal) var(--ease-standard);
&.is-open { opacity: 1; }
}Hero entrance sequence
Stagger the headline, paragraph, and CTA with CSS custom property delays. No JavaScript needed.
---
// Hero.astro
---
<section class="hero">
<h1 class="hero-headline" style="--delay: 0ms">...</h1>
<p class="hero-body" style="--delay: 100ms">...</p>
<a class="hero-cta" style="--delay: 200ms">...</a>
</section>
<style>
.hero-headline,
.hero-body,
.hero-cta {
animation: fade-up var(--duration-slow) var(--ease-decelerate) var(--delay, 0ms) both;
}
@keyframes fade-up {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
.hero-headline,
.hero-body,
.hero-cta {
animation: none;
}
}
</style>Scroll animation patterns
Option A — Scroll-driven animations (CSS only, modern browsers)
No JavaScript, no IntersectionObserver. Uses animation-timeline: view().
.reveal {
animation: fade-up-reveal linear both;
animation-timeline: view();
animation-range: entry 0% entry 30%;
}
@keyframes fade-up-reveal {
from {
opacity: 0;
transform: translateY(24px);
}
to {
opacity: 1;
transform: translateY(0);
}
}Named view timeline for precise control:
.product-image {
view-timeline-name: --product;
view-timeline-axis: block;
}
.product-copy {
animation: slide-in linear both;
animation-timeline: --product;
animation-range: contain 20% contain 60%;
}Option B — IntersectionObserver (broader support)
For Svelte 5 components, use an action:
// shared/src/utils/animate.ts
export function reveal(node: Element, { delay = 0, y = 16 } = {}) {
const motion = matchMedia('(prefers-reduced-motion: reduce)').matches;
if (motion) return {};
node.style.cssText = `opacity:0;transform:translateY(${y}px);transition:opacity 0.5s,transform 0.5s`;
const io = new IntersectionObserver(([entry]) => {
if (!entry.isIntersecting) return;
setTimeout(() => {
node.style.opacity = '1';
node.style.transform = 'translateY(0)';
}, delay);
io.disconnect();
}, { threshold: 0.15 });
io.observe(node);
return { destroy: () => io.disconnect() };
}<script>
import { reveal } from '$lib/utils/animate';
</script>
<div use:reveal={{ delay: 100 }}>...</div>Stagger grid
---
const items = [...];
---
<ul class="grid grid-cols-3 gap-6">
{items.map((item, i) => (
<li class="reveal" style={`--delay: ${i * 50}ms`}>
<Card {item} />
</li>
))}
</ul>Available patterns
| Pattern | translateY | scale | filter | Notes |
| fade-up | 24px → 0 | — | — | Default for most content |
| fade-left | — | — | — | translateX(24px → 0) |
| fade-right | — | — | — | translateX(-24px → 0) |
| scale-in | — | 0.92 → 1 | — | Good for cards |
| blur-in | — | — | blur(8px) → 0 | Hero images |
| product-reveal | 16px → 0 | 0.96 → 1 | — | Combine for product pages |
| timeline-reveal | 8px → 0 | — | — | Items along a timeline |
| stagger-grid | 24px → 0 | — | — | Grid with --delay per item |
Modern CSS 2025/2026 features
@starting-style — enter transitions without JavaScript
dialog {
opacity: 1;
transform: scale(1);
transition: opacity 0.2s, transform 0.3s var(--ease-bounce),
overlay 0.3s allow-discrete, display 0.3s allow-discrete;
@starting-style {
opacity: 0;
transform: scale(0.92);
}
&:not([open]) {
opacity: 0;
transform: scale(0.92);
}
}transition-behavior: allow-discrete
Required when transitioning display or visibility from none to block. Without it, the element snaps immediately.
.popover {
display: none;
transition: opacity 0.2s, display 0.2s allow-discrete;
&.is-visible {
display: block;
opacity: 1;
}
}:has() for state-based animation
/* Animate card when its checkbox is checked */
.card:has(input:checked) {
transform: scale(1.02);
box-shadow: 0 0 0 2px var(--color-primary);
}
/* Stagger only when section is in viewport */
.section:has(.is-visible) .item {
animation: fade-up 0.4s var(--ease-decelerate) both;
}color-mix() for OKLCH hover tints
.badge {
background: var(--color-accent);
transition: background var(--duration-fast);
&:hover {
background: color-mix(in oklch, var(--color-accent) 85%, white);
}
}CSS Nesting — keep animation rules co-located
.card {
transition: transform 0.3s, box-shadow 0.3s;
&:hover { transform: translateY(-4px); }
&:active { transform: translateY(-1px) scale(0.98); }
& .card-image {
transition: transform 0.5s var(--ease-emphasized);
}
&:hover .card-image { transform: scale(1.04); }
}Tailwind v4 implementation patterns
motion-safe and motion-reduce variants
Always pair these — define the reduced-motion state first as the default, enhance for motion-safe:
<!-- Default: instant, motion-safe: animated -->
<div class="opacity-0 motion-safe:animate-fade-up">...</div>
<!-- Or: always show, but remove hover motion for reduce -->
<button class="motion-safe:hover:-translate-y-0.5 motion-safe:transition-transform">...</button>Custom @utility animations
@utility animate-fade-up {
animation: fade-up var(--duration-slow) var(--ease-decelerate) var(--delay, 0ms) both;
}
@utility animate-scale-in {
animation: scale-in var(--duration-normal) var(--ease-bounce) var(--delay, 0ms) both;
}
@keyframes fade-up {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes scale-in {
from { opacity: 0; transform: scale(0.92); }
to { opacity: 1; transform: scale(1); }
}group-hover for compound effects
<div class="group card-lift">
<img class="transition-transform duration-500 group-hover:scale-[1.04] overflow-hidden" />
<span class="transition-opacity duration-200 opacity-0 group-hover:opacity-100">View</span>
</div>prefers-reduced-motion — non-negotiable
Every animation must have a reduced-motion fallback. Use one of these three approaches — pick the one that fits the context:
CSS media query (scoped styles):
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}Tailwind variant (per element):
<div class="motion-safe:animate-fade-up">...</div>JavaScript (runtime check for Svelte actions / JS-driven animations):
const prefersReduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReduced) return; // skip animation setup entirelyDo not merely slow down animations for reduced-motion — remove them. The preference is about vestibular impact, not aesthetics.
Micro-interaction details (polish)
Exact values — these are thresholds, not suggestions:
- Never
transition: all. Always name the properties:transition-property: transform, opacity.allanimates unintended properties and can trigger layout. - Scale on press:
scale(0.96). Never below0.95— anything smaller feels exaggerated. (0.97idle-press elsewhere in this doc is fine;0.96is the tactile default for buttons.) - Interruptible vs one-shot. Use CSS transitions for interactive state changes (hover/active/focus) — they can be interrupted mid-flight. Reserve
@keyframesfor staged sequences that run once (hero entrance, toast). Don't drive interactive state with keyframes. - Icon swaps: cross-fade, don't toggle
display. Animateopacity 0→1,scale 0.25→1,blur 4px→0. With a motion library:spring, duration 0.3, bounce 0(bounce always 0). Without one: keep both icons in the DOM (oneabsolute) and cross-fade withcubic-bezier(0.2, 0, 0, 1). - Skip entrance animation on first paint where a reveal would otherwise replay on load/tab-restore (e.g.
initial={false}in AnimatePresence). Reveals must enhance already-visible content — never gate visibility on a transition that pauses on hidden tabs.
Quality checklist
Before delivering any animation code, verify each item:
- Purposeful — does this motion communicate something the user needs to know?
- GPU-only — animates only
transform,opacity,filter, orclip-path - Reduced-motion handled — fallback defined at CSS, Tailwind, or JS layer
- Duration on-token — uses
--duration-*variables, not arbitrary values - Scale within bounds —
≤ scale(1.05)for hover,≤ scale(1.02)for idle pulse - Movement within bounds — translate
≤ 12pxexcept for intentional drawer/modal slides - Touch-friendly — hover effects are additive, not required for usability
- No layout animation — no
width,height,margin,padding,top,left - Exit faster than enter — exit transition
≤ 66%of enter duration - No will-change abuse — only on elements that will animate within 100ms
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.

