The internet is full of color, animations, and graphic effects that can make websites both captivating and overstimulating. As frontend enthusiasts and professionals, we need to balance vibrant visuals with accessible, user-centered options for those who prefer a more subdued experience.
In this article, we’re going to do more with less by taking a look at the below items:
prefers-reduced-motion
and prefers-color-scheme
to manage animations and themes@media
rules to apply user preferencesprefers-reduced-data
that will minimize data use for users with limited connectivityFor many users, animations can enhance their experience on a website, but they may impede others. Too much motion can cause discomfort or be a distraction, plus it could cause performance issues.
The prefers-reduced-motion
media query checks if a user has enabled settings on their computer to limit website animations. You can modify or completely disable animations for users who prefer reduced motion.
To get started, let’s create a webpage with some animation. How about an animated striped background?
Here’s the HTML for the page:
<div class="container"> <div class="content"> <h1>User Preferences</h1> <p>prefers-reduced-motion</p> </div> </div>
And here’s the CSS:
.container { position: relative; width: 100%; height: 100%; &::before { position: absolute; content: ""; top: 0; left: 0; height: 100%; width: calc(100% + 110px); background: repeating-linear-gradient( 45deg, #553c9a 0%, #553c9a 25%, #301934 25%, #301934 50% ); background-size: 110px 110px; animation: animateStripes 2s linear infinite; } } @keyframes animateStripes { to { transform: translateX(-110px); } }
Here’s how it looks with the animated stripes:
The no-preference
syntax is for users with no preference settings while reduce
is for those who do. You can completely disable or modify animations for the users who prefer reduced motion.
Here’s how to disable the moving background using the prefers-reduced-motion media
query:
@media (prefers-reduced-motion: reduce) { .container::before { animation: none; } }
Side note: On devices that run Windows 11, you can disable animations by going into Settings, selecting Accessibility, then Visual Effects, and toggling off Animation Effects. The process is similar for nearly every type of device/operating system.
Here’s a CodePen:
See the Pen
Scroll animations with `prefers-reduced-motion` media query by Oscar-Jite (@oscar-jite)
on CodePen.
You can choose to change the type of animation instead of disabling them. For instance, instead of a slide-in transform
animation, you use a fade-in animation for users who prefer reduced motion.
If you use scroll animations with elements sliding in from one side of the page, you can switch to a simpler effect, like a fade-in.
Here’s CSS for a simple scroll animation:
.box { transform: translateX(100%); opacity: 0; transition: transform 0.5s linear, opacity 0.5s linear; } .reveal { transform: translateX(0); opacity: 1; } @keyframes reveal { to { transform: translateX(0); opacity: 1; } }
In this example, the box
elements will fade in from the right side of the webpage and move towards the left. This movement is controlled by the transform
property, so you can simply remove it for users who prefer reduced motion:
@media (prefers-reduced-motion: reduce) { .box { transform: translateX(0); } }
Users with no-preference
will see this when they scroll:
And here’s what users with reduce
will see:
With the prefers-reduced-motion
media query you can tone/slow down complex animations or disable them entirely based on what the user wants.
Here’s a CodePen to interact with where you can disable animations on your device to see the difference:
See the Pen
Scroll animations with `prefers-reduced-motion` media query by Oscar-Jite (@oscar-jite)
on CodePen.
Users with vestibular disorders like motion sickness and vertigo may become disoriented or dizzy when looking at animations. Animations can also be distracting for users who prefer to have a simple UI.
Having the option of reduced motion will make websites much more comfortable to use for users sensitive to motion.
It’s now common practice for websites and applications to have the option of switching from a light theme to a darker one. Some websites give you an extra option based on system preferences.
The prefers-color-scheme
media query detects if a user prefers dark or light themes. The users can get a default theme based on their device settings.
Here’s a webpage with light colors:
This is what users will see if their default theme is light. You can then use the prefers-color-scheme
to create the dark theme:
@media (prefers-color-scheme: dark) { #main { background-image: repeating-linear-gradient( 45deg, #553c9a, #553c9a 50px, #3a1e4f 50px, #3a1e4f 100px, #301934 100px, #301934 150px ); } nav{ background: rgba(0, 0, 0, 0.5); } .logo a, nav ul li a{ color: #b393d3; } .content { background: rgba(0, 0, 0, 0.5); } .content h1 { color: #b393d3; } .content p{ color: #b393d3; } }
Writing out the CSS rules like this for both light and dark modes might be too much work, especially when several properties share the same values. Using variables to map out the color schemes will help you avoid repetition:
:root { --bg-color: repeating-linear-gradient( 45deg, #876a9e, #876a9e 50px, #b393d3 50px, #b393d3 100px, #a288b2 100px, #a288b2 150px ); --nav-main-bg: rgba(255, 255, 255, 0.1); --text-color: #301934; } @media (prefers-color-scheme: dark) { :root { --bg-color: repeating-linear-gradient( 45deg, #553c9a, #553c9a 50px, #3a1e4f 50px, #3a1e4f 100px, #301934 100px, #301934 150px ); --nav-main-bg: rgba(0, 0, 0, 0.5); --text-color: #b393d3; } }
Here’s a screenshot of the same page as before but with dark mode activated:
Here’s a CodePen you can interact with:
See the Pen
Prefers-color-scheme (dark/light theme) by Oscar-Jite (@oscar-jite)
on CodePen.
The prefers-color-scheme
is not limited to colors only; you can use it to swap out images:
:root { --bg-image: url(/plufow-le-studio-bPEsxCiXFIU-unsplash.jpg); --nav-content-bg: rgba(255, 255, 255, 0.1); --text-color: #301934; } @media (prefers-color-scheme: dark) { :root { --bg-image: url(/plufow-le-studio-lFjIBTDlYuo-unsplash.jpg); --nav-content-bg: rgba(0, 0, 0, 0.5); --text-color: #b393d3; } }
Here’s a screenshot of the webpage in light mode:
And here’s the page in dark mode:
Be sure to test color contrasts before using them to ensure better readability. There are several tools available that can help you pick the colors to use.
Consider every possible element that needs updating when switching themes, not just the background and text. This is why storing the themes using CSS variables is a good idea, you may need to provide alternates for buttons, shadows, borders, links, and more.
The most straightforward way to implement user preferences is to use the @media
rule. You must specify the preference for motion or themes, otherwise, the CSS rules inside the media query will override any other rules or device settings.
This means that for motion preferences, you must specify if it’s reduce
or no-preference
, and for themes, it’s light
or dark
:
/* Wrong */ @media (prefers-reduced-motion) @media (prefers-color-scheme) /* Correct */ @media (prefers-reduced-motion: reduce) @media (prefers-reduced-motion: no-preference) @media (prefers-color-scheme: dark) @media (prefers-color-scheme: light)
This can be useful when testing your code, but be sure to specify the exact preference before implementation.
User preferences can also be implemented with JavaScript. You can add a new class to specific elements based on user preferences.
Using our first example with the animated stripes, here’s how to check for user preferences with JavaScript:
const stripes = document.querySelector('.container'); if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { stripes.classList.add('reduced-motion'); } else{ stripes.classList.remove('reduced-motion'); }
Here’s the CSS:
.container.reduced-motion::before { animation: none; }
Note that pseudo-elements are not part of the DOM and can’t be directly selected in JavaScript, hence this approach.
Custom HTML data attributes and JavaScript allow you to implement user preferences. Data attributes allow you to store information on HTML elements without affecting the document’s structure. They use the data prefix and can be easily manipulated using JavaScript:
const containers = document.querySelectorAll('.container'); // update animations based on user preference function updateAnimationPreference() { if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { // remove attribute if user prefers reduced motion containers.forEach((stripes) => { stripes.removeAttribute("data-animated"); }); } else { // add attribute if there's no preference containers.forEach((stripes) => { stripes.setAttribute("data-animated", "true"); }); } } updateAnimationPreference(); // Listen for changes to motion settings window.matchMedia('(prefers-reduced-motion: reduce)').addEventListener('change', updateAnimationPreference)
Here’s the CSS:
.container::before { animation: none; } .container[data-animated="true"]::before { animation: animateStripes 2s linear infinite; } @keyframes animateStripes { to { transform: translateX(-110px); } }
While still experimental, prefers-reduced-data
is a proposed media query that allows websites to detect if users prefer to save data.
It uses the same syntax as the prefers-reduced-motion
media query, which is reduce
for users who prefer lightweight content and no-preference
for users with no data preference.
Some of its potential applications include reducing high-resolution images, loading alternate fonts, disabling autoplay videos, and lazy-loading non-critical content. This media query could help improve load times for users on limited or costly data plans, or with unreliable internet connections.
Respecting user preferences is crucial for enhancing every user’s experience. In this tutorial, you learned how to use the prefers-reduced-motion
and prefers-color-scheme
media query to detect a user’s motion and theme settings. There are also @media
rules for contrast and transparency preferences.
As a web developer, you’re the architect with the power to make every website comfortable, accessible, and efficient for every type of user.
As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.
Modernize how you debug web and mobile apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowValidating and auditing AI-generated code reduces code errors and ensures that code is compliant.
Build a real-time image background remover in Vue using Transformers.js and WebGPU for client-side processing with privacy and efficiency.
Optimize search parameter handling in React and Next.js with nuqs for SEO-friendly, shareable URLs and a better user experience.
Learn how Remix enhances SSR performance, simplifies data fetching, and improves SEO compared to client-heavy React apps.