Motion UI and animation

Use Motion UI's transition classes, write custom animations with its mixins, and keep motion respectful of reduced-motion preferences.

Transition and animation classes

Motion UI is a small library of keyframes and transition helpers, distributed separately from Foundation. Its classes work on any element, but they are most useful where a Foundation component accepts an animation name — reveal, off-canvas and toggler all take them.

/* app.scss */
@import "settings";
@import "motion-ui";

/* Two switches: transitions are the in/out effects, animations are the
   looping or one-shot keyframe effects. Import only what you use. */
@include motion-ui-transitions;
@include motion-ui-animations;

/* Or generate a specific set to keep the CSS small: */
// @include mui-series(...);
<!-- transition on a toggler -->
<div data-toggler data-animate="slide-in-down slide-out-up" class="callout">
  <p>Content that slides in and out.</p>
</div>

<!-- animation on any element -->
<div class="animated wobble">Attention</div>
<div class="animated fade-in fast">Fast fade</div>
<div class="animated slide-in-right slow">Slow slide</div>

<!-- a reveal driven by Motion UI names -->
<div class="reveal" id="confirm" data-reveal
     data-animation-in="slide-in-down fast" data-animation-out="slide-out-up fast">
  <h2 class="h5">Confirm deletion</h2>
  <button class="button" data-close>Cancel</button>
</div>
CategoryExamplesApplied by
Slideslide-in-down, slide-out-leftClass, or the data-animate attribute
Fadefade-in, fade-outSame
Zoomzoom-in, zoom-outSame
Hinge and spinhinge-in-from-top, spin-cwSame
Speed modifierfast, slowAppended to the effect name
Easing modifierlinear, easeAppended after the speed
💡
Motion UI classes animate with CSS transform and opacity, which the compositor can handle without layout work. Avoid animating width, height or top on a large element — those trigger layout on every frame and are the usual cause of janky motion.

Custom animations with the mixins

/* A one-off keyframe animation defined in your own Sass */
.my-notification-enter {
  @include mui-animation(slide(up, 20px), fade());
}

/* Register a reusable named animation */
@include mui-animation(
  $name: 'card-flip',
  $animation: (perspective(800px), rotateY(90deg) fade()),
  $duration: 400ms,
  $timing: ease-out,
  $delay: 0s,
  $iteration: 1,
  $direction: normal,
  $fill-mode: both
);

/* Use it like any other Motion UI animation */
.card.is-flipping {
  @include mui-animation(card-flip);
}

/* Transition helper for hover states you control yourself */
.panel-hover {
  @include mui-transition(transform 200ms ease-out, box-shadow 200ms ease-out);
  &:hover { transform: translateY(-2px); box-shadow: 0 6px 18px rgb(0 0 0 / 0.08); }
}
// Sequence animations without a timeline library: mui-series emits the classes
// with staggered delays, so the CSS does the scheduling.
// In markup, add the series wrapper and let the delays play out:
// <ul class="mui-series"> ... </ul>

// If you need real control, use the animationend event.
const card = document.querySelector('.card');
card.addEventListener('animationend', (event) => {
  if (event.animationName === 'card-flip') card.classList.remove('is-flipping');
}, { once: true });

Reduced motion and when not to animate

/* Motion UI ships a reduced-motion guard, but your own animations need one too.
   This is the pattern: keep the state change, drop the movement. */
@media (prefers-reduced-motion: reduce) {
  .animated,
  .is-flipping,
  [data-animate] {
    animation: none !important;
    transition: none !important;
  }
}

/* If the animation carried meaning, replace it with an instant but visible
   change — a border, a colour, an icon — rather than deleting it silently. */
@media (prefers-reduced-motion: reduce) {
  .panel-hover:hover {
    transform: none;
    outline: 2px solid var(--primary);
  }
}
  • Animation is feedback, not decoration. If removing it makes the interface confusing, the animation was load-bearing and needs a non-motion equivalent.
  • Keep interactive transitions under about 200ms. Longer feels sluggish when the user is waiting for a response to their click.
  • Never animate something the user must read while it moves — a moving paragraph is hostile to anyone with a reading difficulty.
  • A single element moving is fine; a page of staggered entrances every time a route changes gets tiring by the third visit.
SituationMotionReduced-motion fallback
Panel expandingHeight or slideInstant show with a visible state change
Notification arrivingSlide plus fadeInstant appear, keep the colour
Loading indicatorContinuous spinStatic label such as "Loading"
Decorative entranceStaggered fadeNothing
Error shakeShakeColour and icon change only

FAQ

Do I need Motion UI if I use CSS animations?
No. It is a convenience library of pre-built keyframes and mixins. If your project has a design system with its own motion tokens, skip it and write the few keyframes you need — you will ship less CSS.
Why does my animation not run a second time?
Re-adding a class that is already present does not restart a CSS animation. Remove the class, force a reflow with void element.offsetWidth, then add it again — or use animationend to clean up as shown above.

Interactive plugins: reveals, tabs, accordions and dropdowns Accessibility and RTL support

Last refreshed 2026-09-18.