Skip to content

Animation with intent: using motion well without overdoing it

Read aloud with highlighting. Audio loads only when you press play.

On this page9 sections

What animation is actually for (feedback, continuity, orientation and a little delight), how to do it well with CSS and the View Transitions API, and a checklist for spotting motion that should be cut.

I love good animation. A button that responds the instant you press it, a panel that slides in from where you tapped, a page that hands off smoothly to the next one: when it's right, you don't notice it. You just feel like the interface is paying attention.

I've also sat through plenty of motion that made things worse: hero sections that make you wait before you can read, lists that bounce on every scroll, page transitions that turn a fast site into a slow one.

Let's discuss what animation is for, how to build it well, and a checklist for when to cut it.

What animation is for

I think every animation should have a job. If I can't name the job, it probably shouldn't ship. The jobs I keep coming back to:

  • Feedback. "I heard you." A button press, a toggle flipping, a field shaking gently on an invalid value.
  • Continuity. "This is the same thing, it just moved." A card expanding into a detail view, or an item sliding into its new place when a list re-sorts.
  • Orientation. "Here's where you are now." A drawer coming in from the side tells you where it lives and how to put it away.
  • Delight. A small, occasional flourish that gives a product personality. Used sparingly, this is great. Used everywhere, it becomes noise.

Notice that three of those four are about understanding, not decoration.

Getting the basics right

Duration

Shorter than you think. Nielsen Norman Group's guidance is that most UI animations sit somewhere between 100 and 500 ms, with simple feedback (a checkbox, a toggle) around 100 ms, and anything approaching 500 ms starting to feel like a drag. My own defaults:

  • small feedback: about 100–150 ms;
  • elements entering or leaving: about 200–300 ms;
  • big, full-screen changes: rarely more than about 400 ms.

Easing

Real things don't move at constant speed. As a rule of thumb:

  • ease-out (fast start, gentle stop) for things entering: they respond immediately and settle;
  • ease-in for things leaving: they gather speed and get out of the way;
  • ease-in-out for things moving from one place to another on screen.

Avoid linear for UI movement. It's fine for spinners and progress bars.

Animate the cheap properties

Browsers can animate transform and opacity without recalculating layout or repainting, so they stay smooth even on modest devices. Animating width, height, top or margin forces layout work on every frame and is where jank comes from.

CSS
/* Prefer this */
.panel {
  transform: translateX(100%);
  opacity: 0;
  transition: transform 250ms ease-out, opacity 250ms ease-out;
}
.panel.is-open {
  transform: translateX(0);
  opacity: 1;
}

/* Rather than animating `right` or `width` */

Respect reduced motion

Some people get dizzy, nauseous or distracted by motion, and operating systems let them say so. The prefers-reduced-motion media query passes that setting to your CSS and JavaScript. WCAG 2.3.3 (Animation from Interactions) asks that motion triggered by interaction can be turned off unless it's essential.

"Reduced" doesn't have to mean "none". Swapping a big slide for a quick fade often keeps the meaning without the movement.

CSS
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

That blanket rule is a decent safety net. Better still is designing a reduced variant for the animations that carry meaning.

View Transitions: continuity without the plumbing

The View Transitions API lets the browser snapshot the page before and after a change and animate between them. For years, smooth page-to-page or state-to-state transitions meant a lot of manual DOM juggling. Now the browser does the heavy lifting.

On my own site I use it in two places, which make nice examples.

A theme-switch wipe. When you pick a new theme, the page doesn't just flash to the new colours. A slanted gap opens from the middle of the screen and spreads outwards, revealing the new theme underneath the old one in about a fifth of a second. It's continuity: you see the change happen rather than wondering what just blinked. Here's a simplified, generic version of the pattern:

JavaScript
function switchTheme(apply) {
  const root = document.documentElement;
  const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  if (reduce || !document.startViewTransition) {
    apply();
    return;
  }
  root.classList.add("theme-switching");
  const transition = document.startViewTransition(apply);
  transition.finished.finally(() => root.classList.remove("theme-switching"));
}
CSS
/* An animatable custom property for the width of the gap */
@property --wipe {
  syntax: "<length-percentage>";
  inherits: false;
  initial-value: 0px;
}

@keyframes split-wipe {
  to { --wipe: 80%; }
}

/* The old snapshot sits on top; a slanted gap opens from the centre */
html.theme-switching::view-transition-old(root) {
  z-index: 1;
  mix-blend-mode: normal;
  mask-image: linear-gradient(
    100deg,
    #000 calc(50% - var(--wipe)),
    transparent calc(50% - var(--wipe)),
    transparent calc(50% + var(--wipe)),
    #000 calc(50% + var(--wipe))
  );
  animation: split-wipe 200ms cubic-bezier(0.7, 0, 1, 1) both;
}

html.theme-switching::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}

Scoping everything under a theme-switching class keeps the wipe separate from the page-navigation transitions. It's also worth handling impatience: if someone clicks through several themes quickly, skip the running transition (transition.skipTransition()) and jump straight to their latest pick rather than queueing up wipes.

Notice the guard at the top: browsers without the API just switch instantly, and so do people who've asked for reduced motion.

The site mid theme switch: a slanted gap opens from the centre, showing the new Nord theme between strips of the old Chalk theme.

Page transitions. Moving between pages gets a short (roughly a third of a second), subtle transition so the site feels continuous rather than flashing white between routes. For multi-page sites, cross-document view transitions can be enabled in CSS with @view-transition { navigation: auto; } in browsers that support it.

The key with both: they're quick, they don't block anything, and they disappear for people who don't want them.

Scroll-driven effects, done right

CSS scroll-driven animations let you tie an animation's progress to scroll position without JavaScript scroll listeners. That's a real performance win. It's also an easy way to make a page exhausting.

What I think works:

  • a reading-progress bar;
  • subtle reveal of content as it enters the viewport, done once;
  • sticky headers that compress gently.

What I'd avoid:

  • scroll-jacking, where the page stops scrolling like a page;
  • heavy parallax on text you're trying to read;
  • anything that makes content unreadable until an animation finishes.

How animation gets abused

The failure modes are predictable:

  • Making people wait. Content that fades in over a second, or an intro that plays before you can click. Every repeat visit pays that tax.
  • Motion for its own sake. Everything bounces, nothing means anything, and the one important animation gets lost.
  • Layout jank. Animating layout properties, or content shifting around as things load and animate in.
  • Ignoring preferences. No reduced-motion path at all.
  • Inconsistency. Five different durations and easings across one product, so nothing feels like it belongs together.

A checklist for cutting an animation

Before shipping, I ask:

  1. What's its job? Feedback, continuity, orientation or delight. If none, cut it.
  2. Does it block the user? If someone can't act until it finishes, shorten it or remove it.
  3. Would I still like it on the twentieth view? Delight fades fast; irritation doesn't.
  4. Is it only transform and opacity? If not, can it be?
  5. What happens with reduced motion? There should be a deliberate answer.
  6. Does it match the rest of the product? Same family of durations and easings.

Wrapping up

Good animation helps people understand what just happened and where they are. It's fast, it's cheap for the browser, and it respects people who'd rather not have it. Bad animation makes people wait, wobbles for no reason and ignores their settings.

Give every animation a job, keep it short, stick to transform and opacity, honour prefers-reduced-motion, and lean on the View Transitions API for continuity. And when in doubt, cut it. Nobody has ever complained that an interface felt too responsive.

Further reading