設計基礎

動效

Create motion with managed keyframes, duration tokens, easing tokens, shorthands, and reduced-motion conditions.

Overview

Motion explains what changed, where attention should go, and whether an action is still in progress. In CSS, that often means animations, transitions, and @keyframes, but the design decision comes first: use motion to give feedback, preserve continuity, clarify hierarchy, or reduce surprise.

Master CSS keeps those decisions close to the element with animate:* recipes, native animation:* and transition:* shorthands, shared duration tokens, shared easing tokens, managed @theme keyframes, and motion preference conditions such as @motion and @reduce-motion.

animation:fade|slow|smooth
animation:zoom|fast|overshoot

Choose motion tokens

Start with the job of the motion, then choose the smallest recipe, duration, and curve that communicates it. Most product UI needs short, quiet motion; long loops and large movement should be reserved for progress, live status, or expressive moments.

Animation recipes

Animation tokens package a named animation recipe. Use animate:* when the preset or project token should own the full animation value.

TokenClassRole / Description
--animate-fadeanimate:fadefade 1s infinite Opacity reveal or exit when layout should stay steady.
--animate-flashanimate:flashflash 1s infinite Temporary attention signal; avoid for persistent states.
--animate-floatanimate:floatfloat 3s ease-in-out infinite Ambient lift for decorative or lightweight emphasis.
--animate-heartanimate:heartheart 1s infinite Positive feedback for likes, favorites, or celebratory moments.
--animate-jumpanimate:jumpjump 1s infinite Playful upward emphasis for expressive interfaces.
--animate-pinganimate:pingping 1s infinite Beacon or live-status pulse around an anchor.
--animate-pulseanimate:pulsepulse 1s infinite Breathing feedback for loading, live, or waiting states.
--animate-rotateanimate:rotaterotate 1s linear infinite Continuous spinner or progress indicator.
--animate-shakeanimate:shakeshake 1s infinite Error or invalid-input attention; keep it short.
--animate-zoomanimate:zoomzoom 1s infinite Scale reveal for popovers, dialogs, and emphasized entrances.

animate:* resolves to animation: var(--animate-*), so updating the token changes every element using that recipe.

Duration scale

Duration tokens describe rhythm. They are shared by animation and transition utilities, so the same token can make an entrance, hover state, delay, or transition feel like part of one system.

TokenClassRole / Description
--duration-fastestanimation-duration:fastest75ms Micro feedback such as pressed states and tiny affordances.
--duration-fasteranimation-duration:faster.1s Quick exits, icon feedback, and very short state changes.
--duration-fastanimation-duration:fast.15s Popovers, fades, short entrances, and hover feedback.
--duration-normalanimation-duration:normal.2s Default interaction transitions when no stronger rhythm is needed.
--duration-slowanimation-duration:slow.3s Standard UI movement and visible state changes.
--duration-sloweranimation-duration:slower.5s Panels, drawers, and larger reveals.
--duration-slowestanimation-duration:slowest.8s Ambient or emphasized motion that should be used sparingly.

Easing scale

Easing tokens describe the curve. Use custom easing tokens for product motion language and native CSS values such as linear, ease-in-out, steps(), or cubic-bezier() when a one-off curve is clearer.

TokenClassRole / Description
--easing-smoothanimation-timing-function:smoothcubic-bezier(.4, 0, .2, 1) Balanced movement for common UI transitions.
--easing-softanimation-timing-function:softcubic-bezier(.33, 1, .68, 1) Gentle reveals and quiet fades.
--easing-crispanimation-timing-function:crispcubic-bezier(.16, 1, .3, 1) Quick feedback with a polished finish.
--easing-snapanimation-timing-function:snapcubic-bezier(.2, 0, 0, 1) Firm settling for compact controls.
--easing-accelerateanimation-timing-function:acceleratecubic-bezier(.4, 0, 1, 1) Exits or elements leaving the screen.
--easing-decelerateanimation-timing-function:deceleratecubic-bezier(0, 0, .2, 1) Entrances or elements arriving on screen.
--easing-overshootanimation-timing-function:overshootcubic-bezier(.34, 1.56, .64, 1) Playful scale or position emphasis.
--easing-rewindanimation-timing-function:rewindcubic-bezier(.36, 0, .66, -.56) Pulled-back exits and reversals.
--easing-springanimation-timing-function:springcubic-bezier(.68, -.6, .32, 1.6) Expressive emphasis; use sparingly.

Namespaces for motion

Motion tokens use focused namespaces so recipes, durations, and easing curves can be mixed inside explicit animation and transition classes.

GroupUtility keysDescription
Animation recipesanimateUse full animation recipes from animate tokens.
Durationanimation, animation-duration, animation-delay, transition, transition-duration, transition-delayShare timing tokens across animations, transitions, and delays.
Easinganimation, animation-timing-function, transition, transition-timing-functionShare curve tokens across animation and transition timing functions.

Keep duration and easing token names readable because they appear inside shorthand class names such as animation:fade|fast|smooth and transition:opacity|normal|standard.


Use motion tokens

Use animate:<token> when the full recipe should come from an --animate-* token.

<div class="animate:fade"></div><div class="animate:zoom"></div>
Generated CSS
@layer theme {    :root {        --animate-fade: fade 1s infinite;        --animate-zoom: zoom 1s infinite    }}@layer utilities {    .animate\:fade {        animation: var(--animate-fade)    }    .animate\:zoom {        animation: var(--animate-zoom)    }}@keyframes fade {    0% {        opacity: 0    }    to {        opacity: 1    }}@keyframes zoom {    0% {        transform: scale(0)    }    to {        transform: none    }}

Use native animation:* shorthand when the animation name, duration, easing, direction, iteration count, fill mode, or delay should be visible in markup.

<div class="animation:fade|fast|smooth"></div><svg class="animation:rotate|slowest|linear|infinite@motion">...</svg><div class="animation:zoom|faster|overshoot|both"></div>
Generated CSS
@layer theme {    :root {        --duration-fast: .15s;        --easing-smooth: cubic-bezier(.4, 0, .2, 1);        --duration-slowest: .8s;        --duration-faster: .1s;        --easing-overshoot: cubic-bezier(.34, 1.56, .64, 1)    }}@layer utilities {    .animation\:fade\|fast\|smooth {        animation: fade var(--duration-fast) var(--easing-smooth)    }    .animation\:zoom\|faster\|overshoot\|both {        animation: zoom var(--duration-faster) var(--easing-overshoot) both    }    @media (prefers-reduced-motion:no-preference) {        .animation\:rotate\|slowest\|linear\|infinite\@motion {            animation: rotate var(--duration-slowest) linear infinite        }    }}@keyframes fade {    0% {        opacity: 0    }    to {        opacity: 1    }}@keyframes rotate {    0% {        transform: rotate(-360deg)    }    to {        transform: none    }}@keyframes zoom {    0% {        transform: scale(0)    }    to {        transform: none    }}

Use explicit property utilities when only one part of the motion should change.

<div class="animation-duration:fast animation-timing-function:crisp"></div>
Generated CSS
@layer theme {    :root {        --duration-fast: .15s;        --easing-crisp: cubic-bezier(.16, 1, .3, 1)    }}@layer utilities {    .animation-duration\:fast {        animation-duration: var(--duration-fast)    }    .animation-timing-function\:crisp {        animation-timing-function: var(--easing-crisp)    }}

Use transition shorthands

Use transition:* for state changes. The property, duration, easing, and delay travel together, but the motion only runs when the state changes.

<button class="transition:opacity|fast|smooth">Save</button><div class="transition:transform|slow|overshoot|150ms">...</div>
Generated CSS
@layer theme {    :root {        --duration-fast: .15s;        --easing-smooth: cubic-bezier(.4, 0, .2, 1);        --duration-slow: .3s;        --easing-overshoot: cubic-bezier(.34, 1.56, .64, 1)    }}@layer utilities {    .transition\:opacity\|fast\|smooth {        transition: opacity var(--duration-fast) var(--easing-smooth)    }    .transition\:transform\|slow\|overshoot\|150ms {        transition: transform var(--duration-slow) var(--easing-overshoot) 150ms    }}

Duration and easing namespaces are shared across animation-duration, animation-delay, transition-duration, transition-delay, animation-timing-function, transition-timing-function, animation:*, and transition:*. Keep the token names readable because they appear inside shorthand class names.


Respect motion preference

Use @motion for non-essential motion so it only runs when the user has not asked the system to reduce motion.

<span class="animate:pulse@motion"></span>

Use @reduce-motion to replace motion-heavy effects with a simpler state.

<div class="animation:zoom|fast|overshoot@motion animation:none@reduce-motion translate:0@reduce-motion"></div>
Generated CSS
@layer theme {    :root {        --animate-pulse: pulse 1s infinite;        --duration-fast: .15s;        --easing-overshoot: cubic-bezier(.34, 1.56, .64, 1)    }}@layer utilities {    @media (prefers-reduced-motion:no-preference) {        .animate\:pulse\@motion {            animation: var(--animate-pulse)        }    }    @media (prefers-reduced-motion:reduce) {        .animation\:none\@reduce-motion {            animation: none        }    }    @media (prefers-reduced-motion:no-preference) {        .animation\:zoom\|fast\|overshoot\@motion {            animation: zoom var(--duration-fast) var(--easing-overshoot)        }    }    @media (prefers-reduced-motion:reduce) {        .translate\:0\@reduce-motion {            translate: 0        }    }}@keyframes pulse {    0% {        transform: none    }    50% {        transform: scale(1.05)    }    to {        transform: none    }}@keyframes zoom {    0% {        transform: scale(0)    }    to {        transform: none    }}

Treat reduced motion as a parallel state, not an afterthought. Keep essential feedback visible through opacity, color, text, layout, or an instant end state when movement is removed.


Customize motion tokens

Override motion tokens in the project CSS entry when your product needs a different rhythm. Keep duration and easing names distinct because both namespaces are shared by animation and transition utilities.

@theme {    --duration-enter: 180ms;    --duration-exit: 120ms;    --easing-standard: cubic-bezier(.4, 0, .2, 1);    --easing-emphasized: cubic-bezier(.16, 1, .3, 1);    --animate-dialog-in: dialog-in var(--duration-enter) var(--easing-emphasized) both;    @keyframes dialog-in {        from { translate: 0 0.5rem; opacity: 0; }        to { translate: 0; opacity: 1; }    }}
<dialog class="animate:dialog-in transition:opacity|exit|standard"></dialog>

Use named tokens for repeated motion decisions. Keep raw durations and raw curves for one-off timing that does not belong in the system yet.


Avoid motion noise

Everything loops, moves far, and uses playful easing.

<button class="animation:jump|slowest|spring|infinite">Save</button>

The element moves only when the state change needs feedback.

<button class="transition:transform|fast|crisp">Save</button>

Duration and easing names overlap.

@theme {    --duration-smooth: 200ms;    --easing-fast: cubic-bezier(.4, 0, .2, 1);}

Names keep their namespace meaning clear.

@theme {    --duration-fast: 150ms;    --easing-smooth: cubic-bezier(.4, 0, .2, 1);}

  • Master UI


© 2026 Aoyue Design LLC.MIT License
Trademark Policy