prefers-reduced-motion and prefers-color-scheme: Respecting User Preferences
User Preference Media Queries
Modern CSS includes media queries that detect user preferences set at the operating system level. These allow your website to automatically adapt to how users want to experience the web.
prefers-reduced-motion
Many users enable reduced motion in their OS settings because animations can cause:
- Vestibular disorders: Dizziness, nausea, headaches from motion
- Seizure risks: Rapid flashing or moving content
- Distraction: Difficulty focusing with constant movement
- Battery concerns: Animations consume more power
Detecting Reduced Motion
/* Default: full animations */
.card {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-8px);
box-shadow: 0 12px 24px rgba(0,0,0,0.15);
}
/* Respect user preference */
@media (prefers-reduced-motion: reduce) {
.card {
transition: none;
}
.card:hover {
transform: none;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
}Global Animation Reduction
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}Motion-Safe Approach
Instead of removing animations, you can take the opposite approach — only add animations when the user has NOT requested reduced motion:
/* No animations by default */
.card {
/* Static styles only */
}
/* Add animations only when user is OK with motion */
@media (prefers-reduced-motion: no-preference) {
.card {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-8px);
}
}This motion-safe approach is considered more inclusive because it starts with no motion.
What to Reduce vs Remove
Not all motion needs to be eliminated. Consider:
- Remove: Parallax scrolling, auto-playing animations, bouncing elements, page transitions
- Reduce: Fade-ins can become instant opacity changes, slides can become simple appears
- Keep: Focus indicators, essential state changes (checkbox toggling)
@media (prefers-reduced-motion: reduce) {
/* Replace slide animation with simple fade */
.modal {
animation: none;
opacity: 1;
}
/* Keep essential feedback but make it instant */
.button:active {
transform: scale(0.98); /* Subtle, quick feedback is OK */
}
}prefers-color-scheme
Detects whether the user prefers a light or dark color scheme:
/* Default light theme */
:root {
--bg: #ffffff;
--text: #333333;
--card-bg: #f8f9fa;
--border: #dee2e6;
--primary: #3498db;
--code-bg: #f5f5f5;
}
/* Dark theme when user prefers it */
@media (prefers-color-scheme: dark) {
:root {
--bg: #121212;
--text: #e0e0e0;
--card-bg: #1e1e1e;
--border: #333333;
--primary: #5dade2;
--code-bg: #2d2d2d;
}
}
/* Apply variables */
body {
background: var(--bg);
color: var(--text);
}
.card {
background: var(--card-bg);
border: 1px solid var(--border);
}Images in Dark Mode
Some images look jarring on dark backgrounds. Reduce their brightness:
@media (prefers-color-scheme: dark) {
img:not([src*=".svg"]) {
filter: brightness(0.9) contrast(1.1);
}
}Dark Mode with Manual Toggle
Combine system preference with a manual toggle for the best experience:
/* System preference */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #121212;
--text: #e0e0e0;
}
}
/* Manual override */
[data-theme="dark"] {
--bg: #121212;
--text: #e0e0e0;
}
[data-theme="light"] {
--bg: #ffffff;
--text: #333333;
}// Toggle theme
function toggleTheme() {
const current = document.documentElement.dataset.theme;
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.dataset.theme = next;
localStorage.setItem('theme', next);
}
// Load saved preference
const saved = localStorage.getItem('theme');
if (saved) {
document.documentElement.dataset.theme = saved;
}Other User Preference Queries
prefers-contrast
Detects if the user wants more or less contrast:
@media (prefers-contrast: more) {
:root {
--text: #000000;
--bg: #ffffff;
--border: #000000;
}
.card {
border: 2px solid #000;
}
}
@media (prefers-contrast: less) {
:root {
--text: #555555;
--border: #e0e0e0;
}
}forced-colors
Detects Windows High Contrast Mode:
@media (forced-colors: active) {
.button {
border: 2px solid ButtonText;
}
.card {
border: 1px solid CanvasText;
}
}prefers-reduced-transparency
@media (prefers-reduced-transparency: reduce) {
.glass-panel {
background: var(--bg); /* Solid instead of transparent */
backdrop-filter: none;
}
}Testing User Preferences
In Chrome DevTools
- Open DevTools (F12)
- Click the three dots menu → More tools → Rendering
- Scroll to Emulate CSS media features
- Toggle prefers-reduced-motion and prefers-color-scheme
In Firefox
- Type about:config in the address bar
- Search for ui.prefersReducedMotion (0 = no preference, 1 = reduce)
Combining Preference Queries
/* Dark mode + reduced motion */
@media (prefers-color-scheme: dark) and (prefers-reduced-motion: reduce) {
.hero {
background: #1a1a2e;
/* No animated gradient, just solid color */
}
}Summary
User preference media queries make your website adapt to how people want to use the web. Use prefers-reduced-motion to respect users who experience discomfort from animations — either remove or reduce motion. Use prefers-color-scheme for automatic dark mode support. Combine system preferences with manual toggles for the best user experience. These aren't nice-to-haves — they are essential for building inclusive, accessible websites.