Gerillass 3.0.0 is out. One gradient mixin replaces two, and text-gradient takes its colours first, so read the migration guide before you upgrade.

Gerillass

v3.0.0

Motion Safe

Type: Mixin
@include motion-safe();

* You can call mixins with or without the gls- namespace (e.g. @include gls-motion-safe();).

Some people turn on reduce motion in their operating system, because animation on screen gives them dizziness, nausea or a migraine. The Motion Safe Sass mixin applies the animation you give it only for everyone else, by writing it inside @media (prefers-reduced-motion: no-preference).

It is one media query, and the point is its direction. Motion written inside the block is opt-in, so a user who asked for less motion never gets it.

Opt in, rather than switch off

The usual way round is to write the animation and then turn it off:

Sass
.card { transition: transform 0.2s; }

@media (prefers-reduced-motion: reduce) {
  .card { transition: none; }
}

That override has to name every animation and every transition on the page, including the ones somebody adds next month, and the one it misses keeps moving. Written the other way, nothing moves unless it is inside the block, so there is no list to keep complete.

Keep the visible state outside the block. When the block does not apply, only the base rule is left. An element hidden in its base rule, opacity: 0, and faded in by an animation inside the block stays invisible for exactly the people who asked for less motion. Put the start of the motion in the keyframes, from { opacity: 0; }, and leave the base rule showing the element. Loadify handles the same case by applying the end state directly.

The media queries were checked to parse in Chrome 152. The reduced-motion setting itself could not be emulated in the browser used for this page, so turn it on in your own system settings to see the examples below stand still.

Examples

A transition that only runs for people who have not asked for less motion. Hover over the card. With reduce motion turned on, the card still lifts, but at once rather than gliding.

Sass
.card {
  @include motion-safe {
    transition: transform 0.2s ease, box-shadow 0.2s ease;
  }
}
CSS
@media (prefers-reduced-motion: no-preference) {
  .card {
    transition: transform 0.2s ease, box-shadow 0.2s ease;
  }
}
Result

Called at the root, the block can hold whole rules. The fade starts from opacity: 0 in the keyframes, not in the base rule, so with reduce motion turned on the badge is simply there.

Sass
@keyframes fade-in {
  from { opacity: 0; transform: translateY(6px); }
}

@include motion-safe {
  .badge {
    animation: fade-in 0.6s ease both;
  }
}
CSS
@keyframes fade-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}
@media (prefers-reduced-motion: no-preference) {
  .badge {
    animation: fade-in 0.6s ease both;
  }
}
Result

What it refuses

Called without a block it has nothing to wrap, and would emit nothing at all, so it stops the build instead.

Sass
.card {
  @include motion-safe;
}
Error: `motion-safe` wraps the motion you pass it, so call it with a block, such as `.card { @include motion-safe { transition: transform 0.2s; } }`.