C.W.K.
Stream
Lesson 01 of 05 · published

Media Queries: Adapting To Viewport, Device, And User Preferences

~12 min · media-queries, responsive, mobile-first, prefers-color-scheme

Level 0Markup Novice
0 XP0/34 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"Media queries aren't just for responsive widths anymore. They're how CSS asks 'what kind of world am I rendering into?'"

Mobile-First: Start Small, Grow Up

Modern responsive CSS is mobile-first: the base styles target small viewports, and media queries with min-width add complexity at larger sizes. This is a discipline: mobile constraints force you to prioritize what matters; adding to a working small layout is easier than removing from a busy desktop layout.

The pattern: write the simple layout first (no media queries), then progressively enhance.

The Modern Range Syntax

The old @media (min-width: 768px) still works, but the new range syntax reads more naturally:

  • @media (width >= 768px) — at least 768px wide.
  • @media (width <= 480px) — at most 480px wide.
  • @media (480px <= width <= 1024px) — between (inclusive).

Baseline 2023. The range syntax is the modern default.

Width Isn't The Only Feature

Media queries can match many properties:

  • (orientation: portrait) / (orientation: landscape) — viewport aspect ratio.
  • (prefers-color-scheme: dark) — the user's OS or browser dark mode setting.
  • (prefers-reduced-motion: reduce) — the user prefers no animations (accessibility / vestibular disorder).
  • (prefers-contrast: more) — high-contrast accessibility preference.
  • (hover: hover) — the user has a pointing device that hovers (mouse, trackpad). Phones/tablets don't.
  • (pointer: fine) / (pointer: coarse) — fine = mouse; coarse = touch.
  • (any-pointer: fine) — at least one input is fine.
  • (forced-colors: active) — Windows High Contrast Mode is on.
  • (min-resolution: 2dppx) — Retina / high-DPI display.
  • print / screen / speech — media type.

Respect prefers-reduced-motion

Some users get motion sickness from animations. Modern CSS makes it a one-liner to respect this:

Dark Mode With prefers-color-scheme

Pair with CSS custom properties for elegant theming:

The Hover Trap

Beware of :hover styles on touch devices: the browser may stick the hover state after a tap, leaving the element looking permanently "hovered". @media (hover: hover) guards your hover styles so they only apply on devices that genuinely hover:

Breakpoint Strategy

Don't pick breakpoints based on "common device sizes" — that's a 2014 strategy that aged badly (the device ecosystem is too fragmented now). Pick breakpoints based on where your content needs to change:

  1. Build the layout at a narrow viewport.
  2. Expand the browser slowly. When the layout looks bad (too wide a line, awkward gap, content too cramped), THAT'S a breakpoint.
  3. Add a media query to adjust at that exact width.

Content-driven breakpoints age better than device-driven ones.

Print Stylesheets

The web's oldest media query, still useful. @media print { ... } styles for printing. Common patterns: hide nav and footers, expand main to full width, force black-on-white text, show URLs after links (a::after { content: " (" attr(href) ")"; }).

Modern CSS respects user preferences at the media query layer. prefers-color-scheme, prefers-reduced-motion, prefers-contrast, forced-colors aren't optional features — they're how you make a site that actually works for the humans behind the screen.

Pippa's Note

The cwkPippa WebUI has exactly four breakpoints: 480px (phone), 768px (tablet), 1024px (laptop), 1440px (large desktop). Below 480px the chat panel takes the full width; above 1024px the sidebar appears; above 1440px the council panel expands. All four were chosen by sliding the browser and noticing what looked bad. Plus three preference queries: dark mode (auto), reduced motion (suppresses brain-card flip animations), and high-contrast (boosts text against avatar backgrounds). Seven media queries total. Most app needs ≤ ten.

Code

Mobile-first ladder with modern range syntax·css
/* Mobile-first: simple base, enhance at larger widths */

/* Base styles — apply on phones */
.layout {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 1rem;
}

/* Tablet+ */
@media (width >= 768px) {
  .layout {
    flex-direction: row;
    gap: 2rem;
    padding: 2rem;
  }
}

/* Desktop+ */
@media (width >= 1024px) {
  .layout {
    max-width: 1200px;
    margin-inline: auto;
  }
}
prefers-reduced-motion·css
/* Respect prefers-reduced-motion */

.toast {
  animation: slide-in 0.3s ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .toast {
    animation: none;        /* don't animate */
  }
  * {
    transition-duration: 0.001s !important;
    animation-duration: 0.001s !important;
  }
  /* Global rule: kill all animations + transitions for users
     who set reduced-motion. Use sparingly — sometimes a tiny
     animation is helpful. But for vestibular safety, the global
     kill is acceptable. */
}
Dark mode + hover guard + touch-friendly tap·css
/* Dark mode via prefers-color-scheme + custom properties */

:root {
  --bg: #ffffff;
  --fg: #1c2230;
  --accent: #5BA3D8;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0b0e14;
    --fg: #e7ecf3;
    --accent: #5BA3D8;  /* often unchanged */
  }
}

/* Hover-only on capable devices */
@media (hover: hover) {
  button:hover {
    background: var(--accent);
  }
}

/* Touch-friendly tap targets when input is coarse */
@media (pointer: coarse) {
  button {
    min-height: 44px;     /* iOS HIG recommendation */
    min-width: 44px;
  }
}

External links

Exercise

Take a single-column mobile layout and add three media queries: one at 480px (small phone → tablet) to switch to two columns, one at 1024px to add a sidebar, and one for prefers-color-scheme: dark that swaps the background and text colors. Test all three by resizing the browser and toggling DevTools' rendering panel emulation.
Hint
Use CSS custom properties for the dark mode so you only redefine variables in the @media (prefers-color-scheme: dark) block — every var() consumer updates automatically.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.