CSS transitions (and animations) how to handle reduced motion. When should you and when shouldn't you reduce motion?
Transitions what are they good for?
Transitions help to show the user what is changing, motion attracts the eye and helps to draw attention to the changing content. It also adds a feeling of skeuomorphic real-world cause and effect. Hover/focus states help to indicate what is and what isn't an interactive element. In the case of internal page links, smooth scrolling helps to orient the user to where the target content is in relation to where they where on the page. Historically, smooth scrolling wasn't preferred as it made the browser's 'Find in page' functionality slower and harder to use, but now as many browsers don't apply smooth scrolling anymore when users search page content, smooth scrolling is becoming more and more common.
What if the user doesn't want motion?
Our users may have requested reduced motion for some pretty serious reasons, for example, vestibular disorder. So it's
important we disable motion for those users. Not everyone is a fan of things moving around so let's not force it on
them, fortunately, we can turn off transitions and animations in the CSS using the
prefers-reduced-motion
media query.
Browser support is growing (See Can I Use). There is still a
little way to go on mobile, but I'm sure it will catch up with desktop before too long.
CSS
In CSS the prefers-reduced-motion
media query.
@media (prefers-reduced-motion: reduce) {
/* styles for reduced motion here */
}
JS
And in JS you can check with the
window.matchMedia
method.
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion)').matches;
if (!prefersReducedMotion) {
// animate stuff
}
Let's take a moment to look at a really annoying thing...
<style>
:root {
--animation-duration: 2s;
}
@media (prefers-reduced-motion: reduce) {
:root {
--animation-duration: 0;
}
}
.annoying-thing {
animation: annoy var(--animation-duration) cubic-bezier(0.3, 0.05, 0.2, 0.8) infinite;
display: inline-block;
transform: translate(0, 0) rotate(0deg) scale(1);
}
@keyframes annoy {
10%, 90% { transform: translate(-1px, 1px) rotate(-8deg); }
20%, 80% { transform: translate(2px, -2px) rotate(8deg); }
30%, 50%, 70% { transform: translate(5px, -5px) rotate(-8deg) scale(1.05); }
40%, 60% { transform: translate(-2px, 2px) rotate(8deg); }
}
</style>
<div class="annoying-thing">Really annoying thing!</div>
So that's annoying, let's request reduced motion (example shown is in MacOS, System Preferences > Accessibility > Display) checking this option should stop the above animation running.
As we have seen to respect the user's preferences in the CSS we need to use the prefers-reduced-motion
media query.
For transitions setting the duration to 0
will just mean it snaps to the new state without any motion. For animations
setting the animation duration to 0
will prevent animations from playing at all so this is a good place to start. As
another option, we could set the animation-play-state
to paused
instead of the default running
. To prevent smooth
scrolling set the scroll-behavior
to auto
. Bearing the above in mind, our CSS might end up looking something like
this...
:root {
--animation-duration: 1s;
--transiton-duration: 0.5s;
}
html {
scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
:root {
--animation-duration: 0;
--transiton-duration: 0;
}
html {
scroll-behavior: auto;
}
}
@media (prefers-reduced-motion: no-preference) {
/* styles here for any parallax effects */
}
Critical animations
With transitions an element is usually just going nicely from one state to another, removing the transition won't
interfere with the user's understanding of the page. However, with animations this might not always be the case. When
setting an animation's duration to 0 it's likely the animation won't play at all so if using
animation-fill-mode: forwards
we would need to consider what the default state for that element should be and set it
manually. Or if an animation is the only visual clue of what is happening, e.g. clicking a 'Buy Now' button triggers an
animation that drops an item into a virtual basket, when we remove the animation we'll need to make sure there is some
other way to notify the user the basket has been updated.
Don't forget testing ♥
When testing we should remember to give our sites the once over with reduced motion preferred. Simply disabling transitions/animations and smooth scrolling when the user has requested reduced motion isn't hard and we should take the time to make the web as an inclusive space as possible.
Resources and Further Reading
- Can I use 'preferes-reduced-motion'
- Reduce screen motion in iOS - Apple
- Reduced motion - A11y 101
- prefers-reduced-motion - MDN
- Designing For Accessibility And Inclusion - Smashing
- Designing Safer Web Animation - A List Apart
- Ask a UXpert: Animation Best Practices for Avoiding Common Mistakes - Adobe Xd
- UI Animation: Please Use Responsibly - UX designs
Hero image by Hans M on Unsplash, opens in a new window