Command Palette

Search for a command to run...

Hectal
PHASE 9Intermediate ~28 min· topic 3 of 7

Topic 9.3

User Preference Queries

In one line

Media queries can also ask about the person and their device: prefers-color-scheme (light or dark), prefers-reduced-motion, prefers-contrast, and hover/pointer (mouse or touch), so your page respects settings people have already chosen in their operating system.

Think of it like this

A good host. When a friend visits, you don't ask them to change for your house. You notice they're sensitive to bright light and dim the lamp; you know they get dizzy on swings and don't push them; you hand a small child the plastic cup, not the glass. User preference queries let your page notice the settings a person has already chosen on their device, and adjust without asking.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

User preference
A setting the person chose on their device, like dark mode or reduced motion, that websites can read.
`prefers-color-scheme`
A media feature that is light or dark, following the system's appearance setting.
`color-scheme`
A CSS property that tells the browser which colour schemes the page supports, so form controls and scrollbars match.
`prefers-reduced-motion`
A media feature that is reduce when the person asked their system for less animation.
Vestibular disorder
A condition of the inner ear and balance system; big on-screen motion can cause dizziness or nausea.
`prefers-contrast`
A media feature that is more, less, custom or no-preference, from the person's contrast setting.
`forced-colors`
active when the system replaces page colours with its own small palette, like Windows Contrast Themes.
`hover` / `pointer`
Media features describing the main input: can it hover, and is it precise (fine, a mouse) or not (coarse, a finger)?

Step by step

01Put colours in custom properties

Write the light theme as the default, with every colour in a variable. Then the dark theme only changes the variables, not every rule.

style.csswhole filecss
:root {
  color-scheme: light dark;
  --bg: #ffffff;
  --text: #1f2937;
  --card: #f3f4f6;
  --accent: #2563eb;
}
body {
  background: var(--bg);
  color: var(--text);
}

02Swap them for dark mode

One query, one place. Every rule that uses the variables follows. Don't use pure black and pure white in dark mode: very high contrast on a dark background is tiring; a dark grey and an off-white read better.

style.csswhole filecss
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #111827;
    --text: #e5e7eb;
    --card: #1f2937;
    --accent: #60a5fa; /* lighter blue keeps contrast on dark */
  }
}

03Calm the motion

Keep big movement for people who haven't asked for less. Inside reduce, turn animations off or swap them for a gentle fade.

style.csswhole filecss
.banner {
  animation: slide-in 600ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .banner {
    animation: fade-in 200ms ease-out; /* or: animation: none; */
  }
  html {
    scroll-behavior: auto; /* no smooth scrolling jumps */
  }
}

04Adapt to touch and mouse

Bigger targets for fingers; hover effects only where hovering exists. The button works the same everywhere; only the extras change.

style.csswhole filecss
@media (pointer: coarse) {
  button, .nav a {
    min-height: 44px;       /* easy to tap */
    padding-inline: 1rem;
  }
}
@media (hover: hover) {
  .card:hover {
    box-shadow: 0 4px 12px rgb(0 0 0 / 0.15);
  }
}

05Strengthen contrast when asked

Faint greys and thin borders are the first things to fix for prefers-contrast: more. In forced colours mode, give elements a real border so they don't disappear when backgrounds are replaced.

style.csswhole filecss
@media (prefers-contrast: more) {
  :root { --muted: #374151; }
  .card { border: 2px solid currentColor; }
}
@media (forced-colors: active) {
  .badge { border: 1px solid; } /* backgrounds may be removed */
}

06Where the answers come from

The browser reads the operating system's settings and the device's inputs, and re-evaluates the queries when they change.

Where the answers come fromdiagram
Rendering diagram…

Try it yourself

  1. 1

    Add a third theme colour

    In the first example, add a --border variable for the card border in both themes and use it. Predict: how many rules do you need to change to restyle the border in dark mode? (One: the variable inside the dark query.)

  2. 2

    A gentler alternative

    In the motion example, instead of animation: none, make the reduced version fade: add @keyframes pulse { to { opacity: 0.4; } } and use animation: pulse 1.5s ease-in-out infinite alternate. Emulate reduced motion and compare.

  3. 3

    Emulate everything

    Open DevTools → Rendering and emulate prefers-color-scheme: dark, prefers-reduced-motion: reduce and forced-colors: active one by one while the JavaScript example is open. Watch its lines change without re-running.

Code & diagrams

Light and dark from one set of variables New tab

Switch your system (or DevTools: More tools → Rendering → "Emulate CSS media feature prefers-color-scheme") between light and dark and run again. Only the variables change.

Preview

Press Run to see the page.

Console

Nothing logged yet.
Respecting reduced motion New tab

Turn on "Reduce motion" (Windows: Settings → Accessibility → Visual effects → Animation effects off; macOS: Accessibility → Display → Reduce motion), or emulate it in DevTools' Rendering panel, then run again.

Preview

Press Run to see the page.

Console

Nothing logged yet.
Reading preferences from JavaScript New tab

The answers are about your device and settings, so they are shown on the page rather than printed to the console. Change a setting while the page is open and the line updates by itself.

Preview

Press Run to see the page.

Console

Nothing logged yet.
Hover effects only where hover exists New tab

On a phone, tapping no longer leaves a "stuck" hover colour, and the links are larger. In DevTools' device toolbar, pick a phone to emulate touch.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Dark mode that forgets `color-scheme`

In the first example, delete color-scheme: light dark; and add an <input> and a long scrolling list, then switch the system to dark.

terminal
$ Page in dark mode
── what you'll see ──
Your colours turn dark, but the text input stays bright white with dark text, and the scrollbar stays light: they don't match the page.

Break #2

Menu that only opens on hover

Hide a submenu with display: none and show it only with .menu:hover .submenu { display: block; }.

terminal
$ Phone or keyboard user
── what you'll see ──
On a phone the submenu can't be opened reliably (a tap sometimes triggers a fake hover, sometimes follows the link), and keyboard users can't reach it at all.

Myth vs fact

Myth

Dark mode means inverting all the colours.

Fact

Inverted colours look harsh and break images and brand colours. Pick a dark palette: dark greys for backgrounds, off-white text, and lighter accents that keep enough contrast.

Myth

Reduced motion means no animation at all.

Fact

It means less movement that could cause discomfort: big slides, zooms, parallax, spinning. Fades and small colour changes are usually fine, and some motion that explains what happened can stay if it's gentle.

Myth

(hover: none) means the device is a phone.

Fact

It describes the primary input only. A tablet with a mouse can report hover: hover; a touch laptop reports its trackpad. Use any-hover and any-pointer when any input matters, and never guess the device type.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    prefers-color-scheme reports the scheme the browser wants for web content, which some browsers let users set separately from the OS. DevTools' Rendering panel can emulate it and also prefers-reduced-motion, prefers-contrast, forced-colors and more, which is the fastest way to test.

  • ▸

    In forced colours mode the browser overrides color, background-color, border-color and others with system colours, and removes box-shadow and background-image on most elements. Use system colour keywords (CanvasText, LinkText, ButtonText, Highlight) inside @media (forced-colors: active) when you must draw something yourself, and forced-color-adjust: none only for things like colour swatches that must keep their real colour.

  • ▸

    A common global safety net: @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; } }. It is blunt; prefer designing a reduced version per component, but it's a reasonable last line of defence.

  • ▸

    prefers-reduced-data and prefers-reduced-transparency are also specified, but browser support is limited (2026); treat them as progressive enhancements and check current support before relying on them.

Remember this

  1. 1

    These are media features like width, but instead of measuring the screen they read a user preference (a choice the person made in their operating system or browser settings) or a capability of the device (what the input can do). You use them exactly like other media queries: @media (prefers-color-scheme: dark) { ... }. There's no permission prompt, and the page updates live if the person changes the setting.

  2. 2

    prefers-color-scheme is light or dark, following the system's appearance setting (many people switch to dark at night). The common pattern keeps colours in custom properties (Phase 3, Topic 3.6) and swaps their values in one place: :root { --bg: white; --text: #111; } then @media (prefers-color-scheme: dark) { :root { --bg: #111; --text: #eee; } }. Also set color-scheme: light dark on :root: it tells the browser the page supports both, so its own parts (form controls, scrollbars, the default page background) switch too. Phase 11 (Topic 11.3) adds light-dark() and a manual theme toggle.

  3. 3

    prefers-reduced-motion: reduce is on when someone has turned on "reduce motion" in their system settings. For people with vestibular disorders (inner-ear conditions that cause dizziness and nausea), large moving, zooming or parallax effects can make them physically ill. Respect it: replace big movement with a fade or nothing, and stop auto-playing animations. Small colour or opacity changes are usually fine. The other value is no-preference; Phase 10 (Topic 10.5) goes deeper.

  4. 4

    prefers-contrast is more when someone asked for higher contrast, less for lower, and custom for a specific forced palette; otherwise no-preference. With more, strengthen faint borders and grey text. A related feature, forced-colors: active, means the system is replacing your colours with a small palette (Windows High Contrast / Contrast Themes). Then you mostly stay out of the way, but make sure things drawn only with backgrounds or box-shadows still have a visible border or outline, because those may vanish.

  5. 5

    hover and pointer describe the primary input. hover: hover means it can hover (a mouse or trackpad); hover: none means it can't (a finger). pointer: fine means accurate (mouse), pointer: coarse means inaccurate (finger), pointer: none means no pointer at all. Use them to make tap targets bigger on touch (pointer: coarse) and to add hover-only effects only where hover exists. any-hover and any-pointer ask whether any connected input can do it, which matters on a laptop with a touch screen.

  6. 6

    Two rules keep this robust. First, never hide something that's only reachable by hover: touch users can't hover, so the content must work without it, and hover should only enhance. Second, a preference query is a hint, not a guarantee: browsers that don't support a feature treat the query as not matching, so write a sensible default outside the query and adjust inside it. JavaScript can read all of these with matchMedia("(prefers-reduced-motion: reduce)").matches.

Explain it without notes

01

How do you build a dark mode that follows the system setting? Mention color-scheme.

02

What is prefers-reduced-motion for, and what should you change when it is reduce?

03

What's the difference between hover/pointer and any-hover/any-pointer?

04

What is forced colours mode, and what can break in it?

Practice

01

Make a page dark-mode aware: light by default, and in dark mode the background #0f172a, text #e2e8f0 and links #93c5fd. Make form controls match too.

02

A hero image zooms slowly forever with animation: zoom 20s linear infinite. Make it stay still for people who prefer reduced motion.

03

Give buttons a minimum height of 44px on touch screens and a darker background on hover only for devices that can hover.

Trade-offs

  • ↔

    Following the system theme needs no UI, but some people want a different theme for one site; offering a toggle as well means storing the choice and handling three states (light, dark, system).

  • ↔

    Adding motion only inside prefers-reduced-motion: no-preference is the safest default, but it means older browsers that don't support the feature see no animation at all.

Done when you can

  • I can build light and dark themes with custom properties and prefers-color-scheme.

  • I set color-scheme so browser controls match the theme.

  • I can tone down motion with prefers-reduced-motion: reduce.

  • I can use prefers-contrast and test forced colours mode.

  • I can use hover and pointer for touch-friendly sizes and hover-only polish.

  • I never make content reachable only by hover.