Technology

Animation Best Practices for Web Interfaces

Timing, easing, performance, and accessibility: principles for animations that enhance UX without frustrating users.

All articles
TechnologyNexaEx TeamApril 1, 2026 7 min read
Animation Best Practices for Web Interfaces

Motion Design: Animating Interfaces Right

Animation isn't decoration. Purposeful motion guides attention, provides feedback, and improves perceived performance. Bad animation annoys users.

Purpose Before Motion

Every animation should have a reason:

  1. Feedback: Button presses, form validation, state changes.
  2. Attention: Draw focus to important elements.
  3. Continuity: Show relationships between states (expand/collapse).
  4. Context: Provide sense of space and navigation.

Animation without purpose is clutter.

Duration and Easing

Timing matters.

  • Quick feedback: 100-200ms. Button tap, checkbox toggle.
  • Page transitions: 300-500ms. Navigation, modal entry.
  • Complex animations: 500ms-1s. Multi-step sequences.

Longer isn't better. 2-second animations feel slow unless they're part of a narrative sequence.

Easing functions control motion feel:

/* Linear: robotic, unnatural */
transition: opacity 0.3s linear;

/* ease-out: snappy, responsive */
transition: opacity 0.3s ease-out;

/* ease-in-out: natural, smooth */
transition: opacity 0.3s ease-in-out;

/* Cubic-bezier: fine control */
transition: opacity 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); /* bounce */

Common patterns:

  • ease-out: Entry animations (element appears, stabilizes).
  • ease-in: Exit animations (element fades away).
  • ease-in-out: Continuous motion (scroll, drag).

Performance: Use GPU

Animate transform and opacity. These are GPU-accelerated and cheap.

/* Good: GPU accelerated */
.element {
  transform: translateX(100px);
  opacity: 0.5;
  transition: transform 0.3s, opacity 0.3s;
}

/* Bad: CPU expensive */
.element {
  left: 100px; /* Causes layout recalc */
  width: 200px; /* Causes repaint */
  transition: left 0.3s, width 0.3s;
}

Why? GPU animation doesn't trigger layout recalcs or repaints. CPU animations do, which blocks frames.

Accessibility: Respect Motion Preferences

Some users experience motion sickness. Respect prefers-reduced-motion:

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Effectively disables animations. In JavaScript:

const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches

if (!prefersReducedMotion) {
  // Add animations
} else {
  // Use instant state changes
}

Common Patterns

Fade in: Element appears gradually.

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.element {
  animation: fadeIn 0.3s ease-out;
}

Slide in: Element enters from edge.

@keyframes slideIn {
  from { transform: translateX(-100%); }
  to { transform: translateX(0); }
}

.element {
  animation: slideIn 0.4s ease-out;
}

Scale up: Element grows from small to full size (feels energetic).

@keyframes scaleUp {
  from { transform: scale(0.8); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

.element {
  animation: scaleUp 0.3s ease-out;
}

Loading spinner: Continuous rotation (never finishes, user understands it's loading).

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.spinner {
  animation: spin 1s linear infinite;
}

Animation Libraries

CSS alone works for simple animations (fade, slide, scale).

Framer Motion (React):

import { motion } from 'framer-motion'

export function Box() {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      transition={{ duration: 0.3 }}
    >
      Content
    </motion.div>
  )
}

Three.js for 3D animations (overkill for most UIs).

Interaction Patterns

Hover states: Subtle scale or color change signals interactivity.

button {
  transition: transform 0.2s ease-out, background-color 0.2s;
}

button:hover {
  transform: scale(1.05);
  background-color: #0056b3;
}

Loading states: Spinner or skeleton screen indicates work in progress.

{loading ? <Skeleton /> : <Content />}

Confirmation animations: Checkmark after form submit feels satisfying.

{submitted && (
  <motion.div
    initial={{ scale: 0 }}
    animate={{ scale: 1 }}
    transition={{ duration: 0.4, type: 'spring' }}
  >
    ✓
  </motion.div>
)}

Testing and Iteration

Animations feel different at different speeds. Test on:

  • Slow networks (Chrome DevTools: throttle connection).
  • Real devices (animations less performant on mobile).
  • Accessible browsers (reduce-motion settings).

Iterate: If animation feels sluggish, reduce duration. If it feels cheap, add easing or layering.

Frequently asked questions

How long should animations be?

100-200ms for quick feedback (buttons), 300-500ms for transitions, 500ms-1s for complex sequences. Longer feels slow.

Should I animate width and height or use transform?

Use transform. Width/height changes trigger expensive layout recalcs. transform and opacity are GPU-accelerated and cheap.

How do I respect user motion preferences?

Check `prefers-reduced-motion` media query. Disable or significantly shorten animations for users who enable it.

Let's build your next idea

One conversation to scope the work, meet the team, and get a proposal — usually within two business days.