ESC

Type to search the knowledge base.

Media Queries Modern Approach

Modern @media — mobile-first ranges, range syntax, preference queries, and when container queries replace viewport breakpoints.

beginner3 min read
  • css
  • media-queries
  • responsive

Media queries still drive viewport-level layout and user preference hooks. The modern approach is mobile-first min-width bands, fewer magic breakpoints, range syntax where supported, and a clear split between page shell (@media) and component (@container) concerns.

Docs: MDN @media, Using media queries, Media Queries Level 4.

Mobile-first baseline

.nav__links {
  display: none;
}

@media (min-width: 48rem) {
  .nav__links {
    display: flex;
    gap: 1rem;
  }
}

Start from the small layout as default CSS, then add complexity at larger sizes. Prefer rem-based breakpoints so they track user font size better than raw px (teams still use px; be consistent).

Range syntax

/* Legacy pair */
@media (min-width: 48rem) and (max-width: 79.99rem) { }

/* Modern range */
@media (width >= 48rem) and (width < 80rem) { }
@media (48rem <= width < 80rem) {
  .layout {
    grid-template-columns: 12rem 1fr;
  }
}

Support is strong in current engines; keep legacy form if your baseline requires it.

Preference queries (not optional)

@media (prefers-color-scheme: dark) {
  :root {
    --surface: #0f1419;
    --text: #e7ecf1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero {
    animation: none;
  }
}

@media (prefers-contrast: more) {
  :root {
    --border: #000;
  }
}

These are user settings, not device widths. Don’t bury them under “nice to have.”

Pointer and hover capability

@media (hover: hover) and (pointer: fine) {
  .card:hover {
    transform: translateY(-2px);
  }
}

@media (pointer: coarse) {
  .button {
    min-height: 44px;
  }
}

Avoid relying on hover-only affordances for essential actions.

Print

@media print {
  .app-nav,
  .toast {
    display: none !important;
  }

  a[href]::after {
    content: " (" attr(href) ")";
  }
}

See also dedicated print stylesheet patterns.

Media vs container

/* Shell: how many columns in the page grid */
@media (min-width: 64rem) {
  .page {
    grid-template-columns: 16rem 1fr;
  }
}

/* Component: card density based on slot */
@container (min-width: 24rem) {
  .card {
    grid-template-columns: 8rem 1fr;
  }
}

If a widget appears in sidebar and main, container queries prevent “viewport is wide but my column isn’t” bugs.

Interview out-loud

“I write mobile-first CSS and layer min-width queries for larger shells. I use preference queries for color scheme and reduced motion, and pointer queries for hover vs coarse input. Component-level responsiveness belongs in container queries. Breakpoints should map to design needs, not every device model.”

Footguns

  1. Desktop-first max-width soup that’s hard to extend.
  2. Too many breakpoints for minor font tweaks — use clamp.
  3. Hover styles as the only way to show controls on touch.
  4. Testing only browser resize, not real device toolbars / dvh.
  5. Hard-coding px breakpoints that ignore user zoom preferences (know the tradeoff).

Breakpoint tokens

:root {
  --bp-md: 48rem;
  --bp-lg: 64rem;
}

/* Cannot use var() in @media in classic CSS — document tokens and duplicate values, or use a preprocessor/lightningcss plugin */
@media (min-width: 48rem) {
  .nav__toggle { display: none; }
  .nav__links { display: flex; }
}

Name breakpoints by layout intent (nav-inline, two-col) in comments even if values are numeric. Avoid device names (iphone, ipad) — they rot.

Testing matrix

Resize alone misses: zoom 200%, large system font, landscape phone, split-screen tablets, and print. Pair media queries with container queries so components survive unusual shells (settings pages with narrow main columns on wide monitors).

Further reading

Related guides