UI/UX Atlas
Responsive & Platform Intermediate

User-Preference Media Features

Respect how people actually want to experience your UI — motion, contrast, color scheme, and more — by reading user-preference media features directly in CSS and design tokens.

7 min read

The full lesson

Every device has a set of system-level preferences. Users pick a color scheme, set motion sensitivity, request higher contrast, or flag a slow connection. CSS user-preference media features expose all of these as signals you can read in stylesheets, JavaScript, and your design token pipeline. Ignoring them means delivering one rigid experience to everyone. Honoring them is one of the highest-leverage accessibility improvements available at the CSS layer.

What User-Preference Media Features Are

User-preference media features are a category of CSS media features defined in the Media Queries Level 4 and Level 5 specs. Unlike geometry features such as min-width, preference features reflect signals the OS surfaces from explicit user settings or detected accessibility needs.

The seven features with meaningful browser support as of 2026 are:

FeatureWhat it detectsPossible values
prefers-color-schemeSystem light/dark modelight, dark
prefers-reduced-motionReduce or minimize animationno-preference, reduce
prefers-contrastRequest more or less contrastno-preference, more, less, forced
prefers-reduced-transparencyDisable translucent surfacesno-preference, reduce
prefers-reduced-dataLow-bandwidth or metered connectionno-preference, reduce
forced-colorsHigh Contrast / Forced Colors Mode (Windows)none, active
prefers-color-gamutSupported display color spacesrgb, p3, rec2020

You query each of these with @media, just like a width breakpoint. The difference is that they respond to the person, not the screen dimensions.

prefers-color-scheme: Dark Mode Done Right

Dark mode is the most widely used preference feature — and the most often done wrong. The outdated approach inverted hex values: #ffffff became #000000, #333 became #ccc. The result is harsh, flat surfaces, invisible drop shadows, and eye-straining pure-black backgrounds.

The modern approach keeps a first-class set of token values for each scheme. Your dark theme is not a transformation of your light theme. It is a separate mapping of the same semantic token names to values that work on dark surfaces.

/* ---- Primitive palette (OKLCH for perceptual consistency) ---- */
:root {
  --color-neutral-100: oklch(98% 0.005 250);
  --color-neutral-900: oklch(12% 0.01 250);
  --color-brand-500:   oklch(55% 0.22 260);
  --color-brand-300:   oklch(75% 0.18 260);
}

/* ---- Semantic tokens: light defaults ---- */
:root {
  --color-surface:     var(--color-neutral-100);
  --color-on-surface:  var(--color-neutral-900);
  --color-interactive: var(--color-brand-500);
}

/* ---- Semantic tokens: dark overrides ---- */
@media (prefers-color-scheme: dark) {
  :root {
    --color-surface:     oklch(10% 0.01 250);  /* near-black, not pure #000 */
    --color-on-surface:  var(--color-neutral-100);
    --color-interactive: var(--color-brand-300); /* lighter for dark surface */
  }
}

Three decisions matter here:

  • Near-black, not pure black. A background of oklch(10% 0.01 250) (roughly #0A0A0E) reads as dark but avoids the harshness and “dead pixel” look of #000000.
  • Elevation uses luminance steps, not shadows. Drop shadows disappear on dark surfaces. Elevated surfaces should be slightly lighter than the base — oklch(15% 0.01 250) for a card above oklch(10%...).
  • Brand colors need separate dark-mode variants. A brand blue optimized for a white surface often fails WCAG 2.2 AA contrast (4.5:1 for normal text) on a dark background. Lighter, chroma-adjusted versions of the same OKLCH hue handle this cleanly.

prefers-reduced-motion: Animation That Respects Sensitivity

prefers-reduced-motion: reduce is set by users with vestibular disorders, migraine sensitivity, ADHD, or simply a preference for less distraction. WCAG 2.2 Success Criterion 2.3.3 (Animation from Interactions, Level AAA) says motion triggered by interaction should be removable by the user. At the AA level, respecting this preference is considered a legal and ethical baseline for most enterprise products.

The best practice is the safe-default pattern: place your animations inside a prefers-reduced-motion: no-preference block. Users without a preference still get the motion. Nothing runs when reduce is active.

/* Define motion under no-preference, not as a global default */
@media (prefers-reduced-motion: no-preference) {
  .card {
    transition: transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
  }

  .modal {
    animation: slide-in 250ms ease-out forwards;
  }
}

If you have a large existing codebase that already defines transitions globally, the override approach is still valid — but it is less robust. New animations added without the guard will escape the protection.

/* Override approach — less preferred but practical for legacy code */
@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;
  }
}

What “reduced motion” means in practice

Reduced motion does not mean zero motion. Users still benefit from:

  • Opacity transitions — a gentle fade-in is generally safe
  • Instant state changes — button press feedback without a delay
  • Transformed positions when navigating, if the transform is imperceptible in duration

What to remove or replace:

  • Parallax scrolling effects
  • Autoplay carousels or looping animations
  • Large entrance/exit slide and scale transitions
  • Complex multi-step sequences
  • scroll-behavior: smooth — this can trigger disorientation during keyboard navigation

prefers-contrast: More Than Accessibility Mode

prefers-contrast: more is commonly set by low-vision users or people in high-glare environments. prefers-contrast: less is rarer but meaningful for users with certain cognitive or visual sensitivities who find high contrast jarring. The forced value indicates the user is running Windows High Contrast / Forced Colors Mode (see forced-colors below).

WCAG 2.2 AA requires a 4.5:1 ratio for normal text and 3:1 for large text and UI components. Under prefers-contrast: more, aim for ratios that comfortably exceed that baseline — 7:1 (the AAA threshold) for body text is a good target.

:root {
  --color-text-secondary: oklch(50% 0.02 250); /* 4.7:1 on light surface */
}

@media (prefers-contrast: more) {
  :root {
    --color-text-secondary: oklch(25% 0.02 250); /* 11:1 on light surface */
    --color-border-subtle: oklch(30% 0.02 250);  /* make muted borders visible */
  }
}

A common outdated mistake is using low-contrast text to “de-emphasize” secondary content. Secondary text that barely passes AA under normal conditions will fail for a significant portion of users. Contrast is not a stylistic choice — it determines whether the content is readable at all.

forced-colors: Windows High Contrast Mode

forced-colors: active means the OS has taken control of a strict set of CSS color properties, remapping them to a limited system palette. You cannot prevent this. You can only adapt gracefully.

The browser exposes a set of CSS system color keywords — ButtonText, ButtonFace, CanvasText, Canvas, LinkText, Highlight, HighlightText, GrayText — that map to the current High Contrast palette. Using these keywords makes your custom components look intentional rather than broken in forced-colors mode.

@media (forced-colors: active) {
  .custom-checkbox {
    /* Browser has removed your box-shadow border trick */
    outline: 2px solid ButtonText;
    background: Canvas;
    color: CanvasText;
  }

  .badge {
    /* Forced-colors removes background-color from non-interactive elements */
    border: 1px solid CanvasText;
    forced-color-adjust: none; /* opt out only when you handle it yourself */
  }
}

Two key rules for forced-colors support:

  1. Never use forced-color-adjust: none as a blanket reset. Only use it on elements where you have provided a full forced-color-aware replacement style.
  2. Test with Windows “High Contrast White” and “High Contrast Black” settings, plus custom themes. Edge DevTools has an emulation panel for this.

prefers-reduced-transparency

Translucent backgrounds — frosted glass effects, backdrop-filter blur, semi-transparent overlays — can be disorienting for users with certain visual or cognitive conditions. macOS and iOS expose a “Reduce Transparency” toggle. The prefers-reduced-transparency: reduce query fires when it is active.

.sidebar {
  background: oklch(97% 0.005 250 / 80%);
  backdrop-filter: blur(20px);
}

@media (prefers-reduced-transparency: reduce) {
  .sidebar {
    background: oklch(97% 0.005 250); /* fully opaque */
    backdrop-filter: none;
  }
}

This feature has good support in Safari and Chrome as of 2026. It is easy to implement and often overlooked in design system audits.

prefers-reduced-data

prefers-reduced-data: reduce signals a metered connection or low data mode. The most impactful use is skipping auto-loaded high-resolution images, web fonts, and video backgrounds.

/* Only load the decorative background video on unrestricted connections */
@media (prefers-reduced-data: no-preference) {
  .hero {
    background-image: url('/hero-bg.webp');
  }
}

/* Skip expensive web fonts and fall back to system stack */
@media (prefers-reduced-data: reduce) {
  body {
    font-family: system-ui, -apple-system, sans-serif;
  }
}

Browser support is currently limited to Chromium-based browsers — Firefox and Safari have not shipped this feature as of mid-2026. Use it as a progressive enhancement on top of baseline performance, not as your sole data-saving mechanism.

Wiring Preferences into a Token Pipeline

The cleanest architecture treats each preference as a token-level swap, not a cascade of overrides scattered across component files. This maps directly to the W3C Design Token Community Group (DTCG) stable format, where each token carries a $value at the primitive layer and is aliased at the semantic layer.

At build time (Style Dictionary, Theo, or a custom transformer), output separate CSS variable files per preference state:

  • tokens.light.css — default
  • tokens.dark.css — loaded under prefers-color-scheme: dark or via a [data-theme="dark"] attribute for user override
  • tokens.contrast-more.css — loaded under prefers-contrast: more
  • tokens.reduced-motion.css — motion token overrides

The [data-theme] attribute approach matters: it lets you expose a manual theme toggle that persists via localStorage, then synchronize it with the system preference on first load via a small inline script — no flash of the wrong theme.

/* Apply system preference by default */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    /* dark token values */
  }
}

/* Honor explicit user override */
[data-theme="dark"] {
  /* dark token values */
}

Do

Define separate token values for each scheme and preference state rather than computing them at runtime. Use the safe-default pattern for motion (guard animations inside prefers-reduced-motion: no-preference). Provide a manual theme toggle that respects both the system preference and a stored user override. Test every preference in browser DevTools emulation before shipping.

Don't

Invert hex values or use filter: invert(1) as a dark mode strategy. Apply blanket forced-color-adjust: none without providing replacement styles. Use prefers-reduced-motion only as an afterthought or a last-pass addition. Treat low-contrast text as a “de-emphasis” design choice without verifying WCAG 2.2 AA compliance across both color schemes.

Testing Preference Features

Modern browsers expose emulation panels specifically for user preferences:

  • Chrome / Edge DevTools: Rendering tab → Emulate CSS media features — lets you force each preference without changing system settings.
  • Firefox DevTools: Responsive Design Mode exposes a color scheme toggle; the DevTools toolbar has a prefers-color-scheme toggle.
  • Safari: Web Inspector → Elements → Force Dark Appearance.

For forced-colors, test in an actual Windows environment or in Edge’s forced-colors emulation (DevTools → Rendering → Emulate CSS media feature forced-colors).

Automated testing with tools such as axe-core, Playwright, or Cypress can snapshot both light and dark themes by injecting the appropriate data-theme attribute. Playwright also supports colorScheme: 'dark' in its browser launch options, which fires the actual prefers-color-scheme media feature.

Takeaways