Skip to content

Instantly share code, notes, and snippets.

@coreyward
Created July 15, 2026 20:55
Show Gist options
  • Select an option

  • Save coreyward/2741e7342b5a5f68b15b5b1a92d0446b to your computer and use it in GitHub Desktop.

Select an option

Save coreyward/2741e7342b5a5f68b15b5b1a92d0446b to your computer and use it in GitHub Desktop.
Shorter, more to-the-point AI guidance on animations, extracted and distilled from Emil Kowalski’s animation skills.

name: interface-animation description: Design, implement, review, and refine UI animation and gesture-driven motion. Use for transitions, entrances and exits, interaction feedback, shared-element and layout motion, drag or swipe behavior, springs, performance, reduced-motion behavior, and motion-system audits.

Interface Animation

Use motion to make an interface clearer, more responsive, easier to follow, or more expressive. Animation is subjective: the values and patterns in this skill are practical starting points, not universal standards. Judge the result in the context of the product, interaction frequency, input method, content, platform, and intended character.

Prefer evidence over doctrine. A value outside a suggested range is not automatically wrong, and code that looks conventional can still feel poor in use. When motion cannot be judged confidently from source alone, inspect the running interface and say what still needs to be tested.

Start with context

Before proposing or changing motion, identify:

  • the framework, animation libraries, browser targets, and supported input methods;
  • existing duration, easing, and spring tokens;
  • where motion is defined and whether components already share conventions;
  • how often each interaction is likely to occur;
  • whether the product should feel crisp, calm, playful, physical, editorial, or restrained;
  • accessibility requirements and representative target devices;
  • whether the task is to implement, review, audit, or find opportunities.

Extend existing conventions when they are coherent. Avoid introducing a parallel motion system for one component without a clear reason.

Decide whether motion helps

A useful animation usually serves at least one purpose:

  • Feedback: confirm that the interface received an input.
  • State: make a change of state legible.
  • Continuity: connect an object or region before and after a change.
  • Orientation: show where something came from, went, or sits in the interface.
  • Attention: direct focus to a meaningful change.
  • Explanation: demonstrate behavior or sequence.
  • Expression: reinforce the product's tone or make a rare moment memorable.

Decoration can be a valid purpose, especially in expressive, editorial, entertainment, or marketing contexts. It should still justify its cost in attention, performance, and repetition.

Frequency is a useful filter:

Context Useful default
Keyboard-driven or repeated constantly Prefer immediate state changes or very subtle, non-blocking feedback. Motion should not make expert workflows feel delayed.
Frequent pointer interactions Keep feedback brief and low-amplitude. Avoid ceremony on every repetition.
Occasional UI such as popovers, drawers, modals, and toasts Standard transitions can improve continuity and orientation.
Rare, first-run, success, or celebratory moments More expressive timing, sequencing, and overshoot may be appropriate.

Do not add motion merely because a state can be animated. Static or immediate changes are often better for dense data, typing, repeated navigation, and information the user is actively reading or manipulating.

Choose an appropriate motion model

Use the simplest model that supports the interaction:

  • CSS transitions: good for state changes with clear start and end values. They naturally retarget from the current computed state when the target changes.
  • CSS keyframes: useful for sequences, loops, stepped effects, and motion with meaningful intermediate states. They are less convenient for rapidly reversible state changes unless explicitly managed.
  • @starting-style: useful for CSS entry transitions when supported by the target browsers.
  • Web Animations API: useful when motion needs imperative playback control without a full animation library.
  • Springs: useful for gesture-driven, interruptible, velocity-aware, or physically expressive motion.
  • JavaScript animation: appropriate when values depend continuously on live input, layout measurements, simulation, or complex orchestration.
  • Shared-element, layout, or view transitions: useful when preserving identity and continuity between views matters more than a simple entrance or exit.

CSS is not inherently better than JavaScript, and springs are not inherently better than eased transitions. Choose based on control requirements, then test the actual behavior and performance.

Timing and easing

For routine interface animation, staying around or below 300ms is a useful rule of thumb. Shorter motion usually feels more responsive, particularly for small distances and frequent actions. Larger spatial transitions may need more time to remain readable and avoid excessive apparent speed.

Starting ranges:

Interaction Starting range
Press or tap feedback 80–160ms
Hover, color, or small state feedback 100–180ms
Tooltip or small popover 120–200ms
Dropdown or select 150–250ms
Modal, drawer, or sheet 200–400ms
Large layout or shared-element transition 200–450ms
Delay between staggered items 30–80ms

Treat these as calibration ranges. Distance, visual mass, screen size, content density, input method, and product character all affect the appropriate duration. A transition should not prevent the user from continuing unless the interaction genuinely requires a committed sequence.

Useful easing tendencies:

  • Ease-out often works well for entrances and immediate responses because visible movement begins quickly.
  • Ease-in-out often works well for objects moving between two established on-screen states.
  • Linear is appropriate for continuous progress, rotation, marquees, and other constant-rate motion.
  • Ease-in can work for short exits or objects accelerating away, but often feels sluggish on entrances because little happens at the beginning.

Built-in CSS easings may be sufficient. When a more pronounced curve is useful, these are reasonable starting tokens:

:root {
  --motion-ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --motion-ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --motion-ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
}

Use asymmetric timing when the phases have different meanings. A deliberate hold may progress slowly, while cancellation or release should usually respond quickly. Exits are often shorter than entrances, but matching durations can be appropriate when visual symmetry matters.

Feedback and responsiveness

Feedback should begin close to the causal input, not only after a completed click or gesture. For direct controls, a pressed state on pointer-down often feels more responsive than feedback that appears only on release. Keep semantic state and input handling independent from decorative timing so animation does not delay focus, navigation, confirmation, or data updates.

A small scale change is one option:

.button {
  transition: transform 140ms var(--motion-ease-out);
}

.button:active {
  transform: scale(0.97);
}

0.97 is a useful starting point, not a requirement. Compact controls may work better with color, fill, shadow, displacement, icon, haptic, or sound feedback. Avoid stacking several feedback effects unless the interaction warrants the emphasis.

For tooltip groups, an initial hover delay can prevent accidental activation, while adjacent tooltips may appear immediately once the user has demonstrated intent. This can make dense toolbars feel faster without removing the useful first delay.

When combining motion, sound, or haptics, align them with the same causal event and keep their timing perceptually synchronized. Reserve multimodal feedback for moments important enough to justify it.

Spatial continuity and origin

Motion is especially useful when it explains a spatial relationship.

  • Let a popover, menu, or contextual surface appear from its trigger when that relationship helps orientation. A modal with no meaningful source relationship may appropriately remain centered.
  • Let a dismissible surface generally return toward the edge or source it came from.
  • Use direction-aware transitions when forward and backward navigation have a meaningful spatial model.
  • Preserve visual identity with shared-element or layout transitions when an object changes container, size, or view.
  • Use translateX(100%) or translateY(100%) when movement should be relative to the element's own dimensions rather than a hardcoded pixel distance.

A subtle scale-and-fade is a practical default for many small entrances:

.popover {
  opacity: 1;
  transform: scale(1);
  transform-origin: var(--transform-origin, center);
  transition:
    opacity 160ms var(--motion-ease-out),
    transform 160ms var(--motion-ease-out);

  @starting-style {
    opacity: 0;
    transform: scale(0.96);
  }
}

Starting from roughly scale(0.95–0.98) often looks less abrupt than scale(0). Very large scale changes can still be appropriate for icons, playful effects, or deliberately abstract motion. Choose the treatment based on the intended visual language rather than a physical-world analogy alone.

Remember that scale() also scales children, including text and icons. That is useful for press feedback but may be undesirable for larger layout changes.

Sequencing and orchestration

Stagger can clarify order, hierarchy, or grouping, but it is decorative in many interfaces. A delay of roughly 30–80ms between items is a useful starting point. Keep the overall cascade short, do not block interaction while it plays, and avoid staggering frequently repeated lists merely for style.

Coordinate properties as one transition rather than tuning each in isolation. Opacity, transform, color, clipping, and layout should reach meaningful states in a coherent sequence. When a crossfade visibly double-exposes two similar states, a very small amount of blur—around 2px—can sometimes bridge them, but blur can reduce clarity and increase rendering cost. Use it only after testing simpler alternatives.

Clip paths and masks

clip-path: inset() is useful for directional reveals, progress fills, hold-to-confirm feedback, and before/after comparisons. The four inset values remove content from the top, right, bottom, and left. For example, this reveals an overlay from left to right:

.overlay {
  clip-path: inset(0 100% 0 0);
  transition: clip-path 200ms var(--motion-ease-out);
}

.is-active .overlay {
  clip-path: inset(0 0 0 0);
}

Masks can provide soft-edged or gradient reveals where a hard clipping edge is too mechanical. Both techniques can be more expensive than a simple transform or opacity change, particularly over large surfaces, so test on representative devices.

Gesture-driven motion

Direct manipulation should preserve the relationship between input and output.

During the gesture

  • Track the object continuously with the pointer rather than animating only after release.
  • Preserve the offset from where the user grabbed the object; do not snap the object center to the pointer.
  • Use Pointer Events and setPointerCapture() so tracking continues outside the original bounds.
  • Add a small directional threshold, often around 8–12px, before committing a gesture when taps and drags compete.
  • Ignore or explicitly handle additional pointers after a drag begins to avoid jumps.
  • Let users cancel or reverse a gesture when the interaction model supports it.
  • Avoid locking input during an animation.

At release

Use both distance and velocity when deciding whether a drag commits. A starting velocity threshold around 0.1 px/ms can work for swipe dismissal, but it must be calibrated for the component, device, and units used by the implementation.

const velocity = Math.abs(distance) / elapsedMs;
const shouldDismiss = Math.abs(distance) >= distanceThreshold || velocity > 0.1;

For carousels, drawers, and sheets, projecting momentum can produce a better target than choosing the nearest snap point from the release position alone:

function project(initialVelocityPxPerSecond, decelerationRate = 0.998) {
  return (initialVelocityPxPerSecond / 1000) *
    decelerationRate / (1 - decelerationRate);
}

const projectedPosition = currentPosition + project(releaseVelocity);
const target = nearestSnapPoint(projectedPosition);

Treat 0.998 as a scroll-like starting point; lower values settle sooner. Pass release velocity into the settling animation when the animation API supports it so the transition from drag to animation does not visibly reset.

Springs and interruption

For motion that may be redirected, begin from the current on-screen value and preserve velocity when retargeting. A neutral spring should usually settle without obvious bounce; add overshoot when the preceding gesture or product character supports it.

In Motion-style APIs, these are reasonable starting points:

// Neutral settling
{ type: "spring", duration: 0.4, bounce: 0 }

// Momentum-driven or mildly playful settling
{ type: "spring", duration: 0.4, bounce: 0.15 }

Frameworks define spring parameters differently, so compare behavior rather than copying values blindly. Visible bounce is usually better reserved for flicks, throws, drag release, or deliberately playful moments than for routine menus and form controls.

At boundaries, progressive resistance often feels better than a hard stop:

function rubberband(overshoot, dimension, constant = 0.55) {
  return (overshoot * dimension * constant) /
    (dimension + constant * Math.abs(overshoot));
}

The exact resistance should be tuned to the surface size and gesture. The principle is continuity: the interface remains responsive while communicating that the user has moved beyond a meaningful limit.

Performance

Animations that primarily change compositor-friendly properties such as transform and opacity tend to perform better, especially when they are large, frequent, or driven every frame. Prefer them as a default for demanding motion, then measure the actual result.

  • Specify animated properties explicitly; avoid transition: all because unrelated changes may animate unexpectedly.
  • Animating layout properties such as width, height, top, or left can cause additional layout and paint work. This is not automatically unacceptable; use it intentionally and profile representative content.
  • Filters, shadows, masks, clip-path, and backdrop-filter can be visually useful but may be costly over large areas or on lower-end devices.
  • Use will-change sparingly and close to the animation rather than leaving many elements permanently promoted.
  • Predetermined CSS or WAAPI motion often behaves well under load; JavaScript is appropriate when the animation needs live input, measurement, or complex control. Test rather than assuming one implementation path is always faster.
  • Avoid updating inherited CSS variables on a large parent every frame when a direct transform on the moving element would touch less of the tree.
  • Check performance while the application is also loading data, rendering content, or doing other realistic work.

Smoothness is not only a frame-rate number. Inspect whether fast movement strobes, whether text remains readable, and whether coordinated properties drift apart.

Accessibility and alternate behavior

Support prefers-reduced-motion as an alternate motion design, not merely a global off switch.

For reduced motion, consider:

  • replacing large translations, parallax, elastic springs, and pronounced zooms with opacity, color, or immediate state changes;
  • removing decorative loops and overshoot;
  • preserving feedback and state clarity;
  • shortening or eliminating motion that is not necessary for comprehension;
  • keeping focus management and interaction timing identical to the standard experience.
@media (prefers-reduced-motion: reduce) {
  .sheet {
    transform: none;
    transition: opacity 120ms ease;
  }
}

Gate hover-only motion to devices that can actually hover:

@media (hover: hover) and (pointer: fine) {
  .card:hover {
    transform: translateY(-2px);
  }
}

Also test keyboard navigation, visible focus, touch behavior, zoomed text, contrast, reduced transparency where relevant, and any full-screen or looping motion. Avoid flashing, strobing, and large continuous movement that users cannot pause or dismiss.

Finding useful animation opportunities

Look for seams where motion may add information rather than decoration:

  • state changes that currently teleport;
  • controls with weak or delayed feedback;
  • contextual surfaces with no visible relationship to their trigger;
  • items moving between containers or views without continuity;
  • gestures that snap after release with no velocity or interruption handling;
  • content insertion or removal that causes a hard layout jump;
  • rare success, completion, onboarding, or empty-state moments that would benefit from expression.

Reject or reduce candidates when motion would repeatedly interrupt reading, typing, navigation, comparison, or expert workflows. A short list of high-confidence opportunities is more useful than animating every seam.

Reviewing or auditing motion

When reviewing an implementation:

  1. Map the motion system and identify high-frequency paths before judging individual values.
  2. Separate observable problems from stylistic preferences.
  3. Cite the relevant component or code location.
  4. Explain the user-facing effect: delayed feedback, lost orientation, non-interruptible motion, visual discontinuity, inaccessible movement, or measurable performance cost.
  5. Give a concrete starting change—properties, duration, easing, origin, or spring values—while noting where visual tuning is still required.
  6. Reuse existing tokens when possible.
  7. Prioritize by user impact and repetition, not by how visually interesting the fix is.

A practical order of intervention is:

  • remove motion that adds delay without useful information;
  • reduce amplitude or duration where repetition makes it tiring;
  • improve feedback, origin, continuity, and interruptibility;
  • address accessibility and measured performance issues;
  • consolidate tokens and add expressive polish last.

Do not fail a design solely because it uses scale(0), exceeds 300ms, animates a layout property, uses keyframes, or chooses a different easing. These are review prompts, not verdicts. The actual interaction, context, and measured result determine whether they are problems.

Verification

Evaluate animation in use, not only in code:

  • watch it at normal speed;
  • slow it to roughly 2–5× duration to inspect origins, easing, overlaps, and synchronization;
  • trigger it rapidly and reverse it mid-flight;
  • test realistic long, short, loading, empty, and error content;
  • test touch gestures on physical hardware when possible;
  • test while the main thread and rendering pipeline are busy;
  • test reduced-motion and keyboard behavior;
  • compare the result with the rest of the product's motion language;
  • revisit subjective tuning with fresh eyes.

When uncertainty remains, describe the specific feel-check or performance test needed rather than presenting a preference as a fact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment