Skip to main content
motion

Microinteractions Should Have a Job Description

Microinteractions that communicate state earn their place. Ones that exist purely for feel are noise. Ask: what does this communicate, to whom, and when?


5 min read

A button press animation that confirms an action has a job. A hover animation that floats a card up 4px and adds a shadow — what's its job? "It feels nice" is not a job description. If you can't say what a microinteraction communicates, to whom, and in what context, you probably shouldn't ship it.

Most animation-heavy UIs have too much animation doing too little work. The problem isn't excess enthusiasm — it's a missing filter for what earns the right to move.


The Job Description Framework

Before adding any animation to a component, ask three questions:

  1. What does this communicate? State change, feedback, spatial relationship, progress, confirmation, error, invitation to interact?
  2. To whom? The user who just took an action? A user scanning the page? A user who needs to notice that something changed?
  3. When? On user interaction, on data change, on system event, continuously?

If you can answer all three, the animation has a job. If you struggle with any of them, the animation is probably decorative noise.

Microinteractions That Do Their Job

Form field validation feedback. The field border turns red, an error message appears, and the field shakes slightly. The shake is functional: it draws the eye to the error, confirms that the submit attempt happened, and directs attention to where the problem is. Job: communicate validation failure, to the user who just tried to submit, on submit attempt. Justified.

Toggle switch state change. The thumb moves from left to right, the track color changes. The motion communicates the direction of change (on/off, not just "it changed") and matches the physical metaphor of a sliding switch. Job: communicate new state and its direction, to the user who just toggled it, on interaction. Justified.

Toast notification entrance. A success message slides in from the top, pauses, then fades out. The entrance catches attention without requiring action. The exit cleans up without a user interaction. Job: communicate async completion, to the user who initiated an action and moved on, on completion event. Justified.

Ripple effect on button click. The Material Design ripple — a circle expanding from the click point. Job: confirm that the click registered, to the user who clicked, on interaction. Arguably justified for touch interfaces where press feedback is otherwise absent. On desktop with a cursor, the cursor itself provides feedback. The ripple is supplemental at best, noise at worst.

Microinteractions That Are Decorative Noise

Card hover lift. The card rises 4px and gets a shadow on hover. What does this communicate? That the card is hoverable? The cursor change does that. That the card is interactive? The visual design should do that before hover. In most cases, this animation exists because it "adds depth" and "makes the UI feel alive." Those aren't jobs. This is decoration. It adds visual complexity, adds to the motion budget for users with vestibular disorders, and communicates nothing that wasn't already clear.

Staggered list entrance. Each list item fades in sequentially on page load. What does this communicate? Usually nothing that couldn't be communicated by the items simply appearing. The stagger creates the impression that the content is loading, even when it loaded instantly. It makes the page feel slower than it is. It's using animation to create a production quality that isn't earned by the content or the interaction.

Button loading state spinner replacing text. The button text swaps to a spinner on click. This one starts functional — it communicates that the action is in progress — but often fails the "to whom, when" test when the spinner runs for less than 200ms. If your API responds in under 200ms, the spinner flash is noise. Debounce the loading state: only show it if the operation exceeds a threshold.

function useDelayedLoading(isLoading: boolean, delayMs = 200) {
  const [showLoader, setShowLoader] = useState(false);

  useEffect(() => {
    if (!isLoading) {
      setShowLoader(false);
      return;
    }
    const timer = setTimeout(() => setShowLoader(true), delayMs);
    return () => clearTimeout(timer);
  }, [isLoading, delayMs]);

  return showLoader;
}

Show the spinner only when the user would otherwise wonder if the click registered. Under 200ms, they won't.

The Budget Metaphor

Animation has a cost — in CPU, in cognitive load, and in accessibility. The motion budget is finite. Every decorative animation spends from the same budget as every functional one. When users have prefers-reduced-motion enabled, the budget collapses to near zero, and only the most justified animations should survive.

Designing with a budget in mind means making deliberate tradeoffs. A product that animates everything equally has no budget — everything feels the same, and nothing stands out. A product that reserves motion for meaningful state changes trains users to notice when something moves.

When your button's confirmation animation and your ambient hover animations feel the same, users stop reading either of them. The job is no longer being done.