Accessibility and User Preferences
Focus styles, contrast, and system settings
CSS media features let you adapt your design to user system preferences — dark mode, reduced motion, high contrast, and more. Respecting these preferences is both an accessibility requirement and good UX: users who need them rely on them for usability or health reasons.
prefers-color-scheme — Dark Mode
Detects whether the user has chosen a dark or light system theme. Implement dark mode with CSS custom properties so the entire theme flips with a single media query.
:root {
--color-bg: #ffffff;
--color-text: #1a1a1a;
--color-card: #f5f5f5;
--color-border: #e0e0e0;
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0f172a;
--color-text: #e2e8f0;
--color-card: #1e293b;
--color-border: #334155;
}
}
body {
background: var(--color-bg);
color: var(--color-text);
}
Allowing User Override with JavaScript
Many sites allow users to override the system preference with a toggle. Store the preference and apply a class that overrides the media query.
// Apply saved preference on load
const saved = localStorage.getItem('theme');
if (saved) document.documentElement.dataset.theme = saved;
// Toggle button
function toggleTheme() {
const current = document.documentElement.dataset.theme;
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.dataset.theme = next;
localStorage.setItem('theme', next);
}
/* Override via data attribute */
[data-theme="dark"] { --color-bg: #0f172a; --color-text: #e2e8f0; }
[data-theme="light"] { --color-bg: #ffffff; --color-text: #1a1a1a; }
prefers-reduced-motion — Reducing Animations
Users with vestibular disorders, epilepsy, or attention sensitivities may request reduced motion. Parallax, large auto-playing animations, and spinning loaders can trigger discomfort. This media feature is the most safety-critical of the preference queries.
/* Provide animations by default */
.card {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
}
/* Remove or simplify when the user prefers reduced motion */
@media (prefers-reduced-motion: reduce) {
.card {
transition: none;
}
.card:hover {
transform: none;
}
/* Stop all auto-playing animations */
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
prefers-contrast — High Contrast
Some users require higher contrast ratios than your default design provides. prefers-contrast: more is supported in Safari and Chrome; use it to increase border widths, remove low-contrast shadows, and boost text contrast.
@media (prefers-contrast: more) {
:root {
--color-text: #000000;
--color-bg: #ffffff;
--color-border: #000000;
}
.button {
border: 2px solid currentColor;
box-shadow: none;
}
.placeholder {
color: #595959; /* meets 4.5:1 against white */
}
}
/* Windows High Contrast Mode (Edge/IE) */
@media (forced-colors: active) {
.button {
border: 2px solid ButtonText;
color: ButtonText;
background: ButtonFace;
}
}
prefers-reduced-data
An emerging feature that lets you serve lighter assets when the user is on a metered or slow connection.
@media (prefers-reduced-data: reduce) {
/* Skip decorative background images */
.hero { background-image: none; }
/* Load lower-quality images via CSS */
img { image-rendering: auto; }
}
Best Practices
- Treat
prefers-reduced-motion: reduceas a hard requirement, not a suggestion - Use CSS custom properties for theming — they make media-query overrides a single block
- Test with your OS set to dark mode and reduced motion enabled, not just in DevTools
- Ensure colour contrast meets WCAG AA (4.5:1 for normal text) in both light and dark themes
- Never use
!importantto fight user stylesheets — honourforced-colorsmode instead