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
lightordark, 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
reducewhen 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,customorno-preference, from the person's contrast setting. - `forced-colors`
activewhen 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.
: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.
@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.
.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.
@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.
@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.
Try it yourself
- 1
Add a third theme colour
In the first example, add a
--bordervariable 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
A gentler alternative
In the motion example, instead of
animation: none, make the reduced version fade: add@keyframes pulse { to { opacity: 0.4; } }and useanimation: pulse 1.5s ease-in-out infinite alternate. Emulate reduced motion and compare. - 3
Emulate everything
Open DevTools → Rendering and emulate
prefers-color-scheme: dark,prefers-reduced-motion: reduceandforced-colors: activeone by one while the JavaScript example is open. Watch its lines change without re-running.
Code & diagrams
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
Console
Nothing logged yet.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
Console
Nothing logged yet.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
Console
Nothing logged yet.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
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.
Break #2
Menu that only opens on hover
Hide a submenu with display: none and show it only with .menu:hover .submenu { display: block; }.
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-schemereports 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 alsoprefers-reduced-motion,prefers-contrast,forced-colorsand more, which is the fastest way to test. - ▸
In forced colours mode the browser overrides
color,background-color,border-colorand others with system colours, and removesbox-shadowandbackground-imageon most elements. Use system colour keywords (CanvasText,LinkText,ButtonText,Highlight) inside@media (forced-colors: active)when you must draw something yourself, andforced-color-adjust: noneonly 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-dataandprefers-reduced-transparencyare also specified, but browser support is limited (2026); treat them as progressive enhancements and check current support before relying on them.
Remember this
- 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
prefers-color-schemeislightordark, 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 setcolor-scheme: light darkon: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) addslight-dark()and a manual theme toggle. - 3
prefers-reduced-motion: reduceis 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 isno-preference; Phase 10 (Topic 10.5) goes deeper. - 4
prefers-contrastismorewhen someone asked for higher contrast,lessfor lower, andcustomfor a specific forced palette; otherwiseno-preference. Withmore, 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
hoverandpointerdescribe the primary input.hover: hovermeans it can hover (a mouse or trackpad);hover: nonemeans it can't (a finger).pointer: finemeans accurate (mouse),pointer: coarsemeans inaccurate (finger),pointer: nonemeans 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-hoverandany-pointerask whether any connected input can do it, which matters on a laptop with a touch screen. - 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
How do you build a dark mode that follows the system setting? Mention color-scheme.
What is prefers-reduced-motion for, and what should you change when it is reduce?
What's the difference between hover/pointer and any-hover/any-pointer?
What is forced colours mode, and what can break in it?
Practice
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.
A hero image zooms slowly forever with animation: zoom 20s linear infinite. Make it stay still for people who prefer reduced motion.
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-preferenceis 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-schemeso browser controls match the theme.I can tone down motion with
prefers-reduced-motion: reduce.I can use
prefers-contrastand test forced colours mode.I can use
hoverandpointerfor touch-friendly sizes and hover-only polish.I never make content reachable only by hover.