Use this Skill for tasks such as the following.
- Add animation to the UI
- Review the quality of motion design
- Improve motions such as hover, popover, tooltip, dropdown, dialog, tab, segmented control, card, button, toast, accordion, scroll-linked animation, View Transitions, etc.
- Decide whether an animation should be removed
- Select property, easing, duration, transform-origin, etc.
prefers-reduced-motionDecide whether to respect@starting-style,transition-behavior,interpolate-sizeSelect entrance and exit animation techniques such as@keyframesPerform naming conventions, scope management, and API-like custom property design for
Basic Principles
- Animation is a means, not an end
- Always distinguish whether the animation is "functional" or "decorative"
- The best animation may sometimes be "no animation"
- UI motion should be fast and feel responsive. What users want is the perceived speed of "it responded immediately"
- Avoid decorative animation in frequently operated UI
- Easing is the most important aspect of animation. Even with the same duration, the perceived speed changes greatly depending on the easing
- Visual changes should feel causally connected to the trigger
- When you want to synchronize multiple visual changes, individual
transitionmay not be sufficient.@keyframesanimation, the Web Animations API, or CSS transitions andtransition-behaviorChoose an appropriate technique, such as a combination of - Animation greatly affects performance
- For accessibility,
prefers-reduced-motionAlways consider how to present it to users who respect
Required Evaluation Order
Step 1: Is the animation necessary?
Always check the following.
- What problem does this motion solve?
- Does it clarify state, causality, and feedback?
- Is it functional or decorative?
- How often will users see this?
- Does it interfere with keyboard operation or rapid operation?
Criteria
| Purpose | Judgment | Example |
|---|---|---|
| Show the causality of state transitions | Necessary | Accordion open/close, tab switching |
| Guide the user's attention to newly appearing elements | Necessary | toast notification, validation error |
| Return feedback for operations | Necessary | Button :active, checkbox check |
| Maintain spatial continuity | Conditional | View Transitions for page transitions, drill-down |
| Expression of brand personality | Allowed only for low-frequency UI | LP hero, onboarding for first-time visitors |
| Merely visual flair | Unnecessary for high-frequency UI | Such as dashboard cards sliding in |
If there is no strong reason, propose deletion.
Step 2: Which implementation form is appropriate?
Step 2A: Selecting easing and timing
Easing
ease-insystems andlinearsystems tend to give a physically unnatural, mechanical impression.linearApply only to continuous motion that should express constant speed (marquee or progress bar).- For appearance and exit,
ease-outuse a system - For movement of something already on screen,
ease-in-outuse a system - CSS standard easing keywords (
ease,ease-in-out, etc.) have weak contrast. If the project has animation tokens, refer to them, andquintsystem orexpouse a system easing- If no tokens exist, the following is recommended
/* ease-out 系(出現・退場向き) */
--ease--out-quint: cubic-bezier(0.22, 1, 0.36, 1);
--ease--out-expo: cubic-bezier(0.16, 1, 0.3, 1);
/* ease-in-out 系(移動向き) */
--ease--in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);
--ease--in-out-expo: cubic-bezier(0.87, 0, 0.13, 1);
- Applying the same
transition-durationとtransition-timing-functionto all properties is crude. Choose curves separately for each property
Timing
- Exception: transitions of large UI such as dialog, drawer, sheet allow
300ms〜500msis allowed - In UI touched dozens of times daily, even a difference of
100msper time greatly increases perceived load
Step 2B: transform-origin and spatial consistency
transform-originAlways set with consideration of the positional relationship with the trigger- Popover expands and contracts starting from the trigger element. The default
centeris inappropriate in many cases- When using CSS Anchor Positioning, consider dynamically switching
transform-originaccording to the Popover placement direction
- When using CSS Anchor Positioning, consider dynamically switching
scale: 0Scaling in from is physically unnatural. Since it is more natural for 'what was there to rise from a slightly distant position,' choose an origin value according to the element size
| Element type | Scale-in origin |
|---|---|
| tooltip, popover | 0.95〜0.98 |
| dropdown menu | 0.92〜0.96 |
| dialog, drawer, sheet | 0.85〜0.92 |
Step 2C: Guidelines for each interaction type
Hover
- The visual change on hover should be
150ms〜200msas a guideline - Since hover does not exist on touch devices,
@media (any-hover: hover)consider separating with- Avoid hover occurring on anchor links where
hrefdoes not exist, such as the current link. Always:any-link:hoverdefine as - Avoid hover occurring on disabled buttons. Always
:enabled:hoverdefine as
- Avoid hover occurring on anchor links where
active (press feedback)
- When pressing a button or the like,
scale: 0.96〜0.98by inserting a short shrink of about - duration は
100ms〜150msis the guideline - Apply only in UIs where conveying the press is important
focus / keyboard
- If there is animation in visual movement or highlighting during keyboard operation, input and display feel misaligned, and the response appears slow
- Animation during keyboard operation is strictly forbidden
:not(:focus-visible)withintransitionやanimationis defined, so that animation is not applied during keyboard focus
tooltip
- To prevent accidental hover activation, the initial display is
300ms〜500msdelayed - During continuous operation (while Warming Up),
0ms〜50msis displayed immediately with - The cooldown after leaving is
300ms〜500msand if another trigger is touched during this period, immediate display continues
Appearance/dismissal (when accompanied by display switching)
display: noneFor the animation from@starting-stylethe initial style is defined withtransition-behavior: allow-discreteでdisplayやoverlayto make it transitionable- Popover API(
[popover])や<dialog>In the animation of @starting-styleNote thatdisplay: nonetotransitionは Firefox、@keyframeswithindisplaydoes not work in Safari. The former is permitted as progressive enhancement
Height animation (accordion, etc.)
height: autoFor animation tointerpolate-size: allow-keywordsconsider using- If ordered to work in Safari and Firefox as well, fall back to the CSS Grid
grid-template-rows: 0fr / 1frpattern - Since it is an animation that causes layout, be careful about performance, but it is acceptable for accordion use
scroll-linked animation
- Implementation priority
- CSS scroll-driven animations(
animation-timeline,view-timelineetc.) IntersectionObserverstate toggle via- Synchronize with JS every frame only when truly necessary
- CSS scroll-driven animations(
scrollTopAlways warn about implementations that read- Criteria for deciding to use Scroll-driven animations
- When there is a value that changes continuously in conjunction with scroll position → CSS scroll-driven animations
- When the state changes only once when an element enters/leaves the viewport →
IntersectionObserver - When complex logic branching is required according to scroll amount → JS (however, controlled by
requestAnimationFrame)
IntersectionObserverWhen firing viaanimation-fill-mode: bothとanimation-play-state: pauseduse the initial hidden pattern combining
.reveal {
animation-name: --fade-in, --translate-from;
animation-duration: 600ms;
animation-timing-function: var(--ease--out-quint);
animation-fill-mode: both;
animation-play-state: paused;
/* アクティブ時に発火 */
&[data-revealed="true"] {
animation-play-state: initial;
}
}
animation-fill-mode: bothBypausedIn thefromframe of keyframes is applied. This causes the element to be at the start point on keyframes (e.g.,opacity: 0,translate: 0 20px) is drawn- By activating (adding attributes, etc.),
animation-play-stateをinitial(=running) the animation fires just by returning to - The advantage of this approach is that for the initial hidden state,
opacity: 0there is no need to separately write etc. Since the animation's starting point becomes the initial display as-is, keyframes can be treated as a single source of truth @media (prefers-reduced-motion: reduce)inanimation-play-statedisable the entire control of, or simplify the animation to fade only
View Transitions
- When handling View Transitions, consider the following separately
- Visual animation of the snapshot (visual movement)
- Actual layout change (DOM diff)
- In SPA,
document.startViewTransition(), and in MPA,@view-transitionuse the at-rule view-transition-nameThe naming of follows the naming conventions for CSS custom properties
Step 2D: @keyframes Naming and design of
@keyframes operates in the global scope, so establish rules for naming and management.
Design principles
- Treat global keyframes like utility classes. Limit to 1 keyframes = 1 property change, and
animation-namecombine with comma-separated - Global keyframes are limited to
from/tothose that complete in 2 frames. Keyframes containing intermediate frames are defined as local, such as in components - Obvious
fromやtoare omitted. Since the element's current value is used for omitted frames, the animation becomes natural along the cascading. If values are hardcoded, jumps occur when the element's current value differs from expectations - If you want to change the animation's starting point or direction depending on conditions, define API-like custom properties within keyframes and inject values from the usage side
/* グローバル keyframes(base/keyframes.css に配置) */
@keyframes --scale-from {
from {
scale: var(--scale-from--x-value, 1) var(--scale-from--y-value, 1);
}
}
/* 利用側: カスタムプロパティで起点を注入 */
[popover] {
--scale-from--x-value: 0.96;
--scale-from--y-value: 0.96;
animation-name: --scale-from;
animation-duration: 200ms;
animation-timing-function: var(--ease--out-quint);
animation-fill-mode: both;
}
Other
animation-fill-modeWhen explicitly specifying,bothit is recommended to use as the first candidate
Step 3: Is performance appropriate?
Basic rendering cost judgment
Always consider from the following perspectives.
- Whether it causes layout / paint / composite
- Whether the main thread or compositor thread is involved
Property selection
| Priority | Method | Remarks |
|---|---|---|
| High | Processed by the compositor transform, opacity, some filter, some clip-path, CSS scroll-driven animations |
Completed with GPU compositing only |
| Medium | From JS, transform / opacity implementation that updates, FLIP pattern |
Main thread is involved but layout/paint is minimal |
| Low | Properties that cause paint (background-color, box-shadow etc.) |
Rendering cost depends on area. Tolerated for hover etc. |
| Avoid | Properties that trigger layout (width, height, padding, inset etc.) |
except when unavoidable, such as accordion |
| High risk | Large radius blur() / backdrop-filter, huge composited layers, updating global CSS custom properties every frame,scrollTop scroll-linked animation that reads |
filter / backdrop-filter notes on
filtermay be hardware-accelerated, but that does not mean it is low-costbackdrop-filterはfiltercan be even more expensive than- The following cases can be counterproductive, so avoid them
- Large blur radius
- Large paint area (
backdrop-filterrequires particular caution) - Layers during animation
- Multiple overlapping effects
Updating custom properties
- Do not update globally applied custom properties every frame
- Update locally scoped custom properties whenever possible
- Because inheritance causes cascading calculations,
@propertyでinherits: falseset to turn off inheritance - Do not let values be inherited that the entire DOM does not need
will-change Handling of
will-changeAs a rule, add it immediately before the animation starts and remove it after completion- Always specifying it can be counterproductive (wasting GPU memory, unnecessarily creating compositing layers)
- As a rule,
will-changeshould not be used, and only when absolutely necessary should it be used while requiring JS to remove it
contain Using
- As needed, specify
contain: contentto limit the scope of impact on layout, painting, and style calculation. However, do not specify it mechanically; be aware of the following side effects- Contained
position: fixedis treated asabsoluteexcept for the top layer - Overflow is clipped
- A stacking context is generated
- Contained
transform independent properties of
- Prioritize clarity of differences and code readability, and use independent properties (
translate,rotate,scale) - However, in the following cases,
transformmay be used- When the execution order of independent properties (translate → rotate → scale) is inconvenient
skew()When using- For 3D composite specifications that are actually harder to read with independent properties
Step 4: Interrupting and canceling animations
Consider the behavior when the next interaction occurs before the animation completes during an operation.
Criteria for judgment
| Situation | Recommended behavior | Implementation approach |
|---|---|---|
| hover → immediate unhover | Reverse playback from an intermediate state | CSS transition |
| dialog opens → immediately closes | Start the exit animation from an intermediate state | @starting-style + transition then automatic.@keyframes in the case of animation-fill-mode and JS control are required |
| Rapid repeated tab switching | Switch immediately, skip the animation | animation: none forced reset, or View Transitions' skipTransition() |
| Sudden change in scroll direction | Immediately follow the new direction from the current position | With CSS scroll-driven animations, it follows automatically |
Principle
- CSS
transitionautomatically performs reverse playback from an intermediate state, so in UIs where interruptions occur frequently, prioritizetransitionprioritize @keyframesanimation is difficult to control when interrupted, so in UIs where interruptions are expected, avoid it or use the Web Animations API'scancel()/reverse()use- For UIs that are rapidly clicked repeatedly (tabs, segmented controls, etc.), shorten the animation itself (
100ms〜150ms) or add a mechanism to skip it
Step 5: prefers-reduced-motion is respected?
Phased response policy
Do not uniformly disable all animations; respond in phases.
| Response level | Details | Target examples |
|---|---|---|
| Disable | @media (prefers-reduced-motion: no-preference) Define only within |
Parallax, auto-looping carousels, marquee,transform -type scroll-linked animation |
| Simplify | Replace motion with crossfade or fade only | Page transition View Transitions, appearance/disappearance of large UI |
| Shorten | Greatly shorten duration (50ms or less) |
tooltip/popover fade-in |
| Maintain | No change needed | Spinners, progress bars, focus ring display |
Criteria for judging "large changes"
If any of the following applies, consider it a "large change" and disable or simplify it.
- Movement of an element occupying 1/3 or more of the screen
- Motion involving rotation
- Zoom (
scalethe amount of change in0.5or more) - Scroll-linked
transform-type animation - Motion that visually shakes or vibrates
Note
- Define all decorative animations other than fade-in/fade-out within
@media (prefers-reduced-motion: no-preference)define within - Even functional animations should be simplified if they fall under "large changes"
Step 6: Final risk check
After passing all Steps, check the following as a final check.
- Is the easing too weak (are you leaving it as a CSS standard keyword)?
-
transform-originDoes it match the trigger? - Is the animation physically unnatural? (
scale: 0from, etc.) - Are you adding motion to keyboard operations?
- Are multiple visual changes out of sync?
- Is this UI that should delay only on the first time and be immediate afterward (tooltip, etc.)?
- Is the behavior on interruption/cancellation taken into account?
- Performance (minimizing layout/paint,
will-changeproper management) -
prefers-reduced-motionIs it appropriate for users who respect - When initial hiding is required for scroll-linked animation,
animation-fill-mode: both+animation-play-state: pauseduse the pattern and define outside keyframesopacity: 0Are you double-defining etc.? -
@keyframesIs the scope and placement appropriate?
Response format
When proposing a new animation
- Judgment: necessary / unnecessary (show the basis from the decision matrix)
- Classification: functional / decorative
- Motion: easing / duration / property / transform-origin
- Implementation policy: CSS only (
transitionor@keyframesor@starting-style)/ CSS + JS / Web Animations API / FLIP - keyframes design(
@keyframeswhen used): scope (global or component-specific) / necessity of API-like custom properties - Behavior on interruption: reverse playback / skip / none
- reduced-motion support: disable / simplify / shorten / maintain
- Risk: accessibility, friction on repeated use, visual inconsistency, performance
When proposing modifications to existing code
- Judgment: maintain / modify / delete
- Reason: UX purpose and frequency of use
- Modification details: specific changes and code examples
- Risk: side effects of the modification
During performance review
- Classification: layout / paint / composite
- Risk: low / medium / high
- Reason: invalidation path and involved threads
- Safer alternative:
transform/clip-path/ observer / local variable /@propertyetc. - Browser notes: Chrome-specific optimizations, Safari / Firefox differences