Episode 1 — Fundamentals / 1.9 — CSS Responsive Design

1.9.b — Media Queries

In one sentence: @media rules let you apply CSS conditionally based on viewport width, user preferences, input capabilities, and more — they are the backbone of every responsive layout.

Navigation: ← 1.9.a — Mobile-First Strategy · 1.9.c — Breakpoint Planning →


1. Basic syntax

@media <media-type>? and (<feature>: <value>) {
  /* rules that apply when condition is true */
}
@media screen and (min-width: 768px) {
  .sidebar { display: block; }
}
  • @media — keyword that opens the block.
  • Media type — optional filter (screen, print, all). Defaults to all if omitted.
  • Media feature — the condition to test (e.g., min-width: 768px).
  • Rules inside — normal CSS that activates when the condition is true.

2. Media types

TypeApplies to
allEvery device (default if omitted)
screenScreens — monitors, phones, tablets
printPrint preview and printed pages
/* Hide nav and ads when printing */
@media print {
  .nav, .ad-banner { display: none; }
  body { font-size: 12pt; color: #000; }
}

Note: tty, tv, handheld, and others are deprecated — only all, screen, and print are reliable.


3. Media features reference

Width-based (most common)

FeatureTests
widthExact viewport width
min-widthViewport value (mobile-first)
max-widthViewport value (desktop-first)
height / min-height / max-heightViewport height (used less often)
/* Mobile-first: add sidebar at 1024px */
@media (min-width: 1024px) {
  .layout { grid-template-columns: 250px 1fr; }
}

Orientation

FeatureValues
orientationportrait (height > width) or landscape (width > height)
@media (orientation: landscape) {
  .hero { min-height: 60vh; }
}

User preferences

FeatureValuesUse case
prefers-color-schemelight, darkDark mode toggle
prefers-reduced-motionreduce, no-preferenceDisable/simplify animations
prefers-contrastmore, less, no-preferenceHigh-contrast adjustments
/* Respect OS dark mode */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1a1a2e;
    --text: #e0e0e0;
  }
}

/* Remove animations for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

Input capabilities

FeatureValuesMeaning
hoverhover, noneCan the primary input hover?
pointerfine, coarse, nonePrecision of primary pointer
any-hoverhover, noneCan any input hover?
any-pointerfine, coarse, nonePrecision of any available pointer
/* Only show hover effects on devices that support hover */
@media (hover: hover) {
  .card:hover { transform: translateY(-4px); box-shadow: 0 8px 24px rgba(0,0,0,0.15); }
}

/* Enlarge targets for coarse pointers (touch) */
@media (pointer: coarse) {
  .btn { min-height: 48px; padding: 0.75rem 1.5rem; }
}

4. Combining conditions

and — all conditions must be true

@media screen and (min-width: 768px) and (orientation: landscape) {
  .dashboard { grid-template-columns: repeat(3, 1fr); }
}

, (comma / or) — any condition can be true

@media (max-width: 480px), (orientation: portrait) {
  .sidebar { display: none; }
}

not — negates the entire query

/* Applies to everything EXCEPT screens ≥ 1024px */
@media not screen and (min-width: 1024px) {
  .wide-table { overflow-x: auto; }
}

Gotcha: not negates the whole query up to the comma, not just the next feature. Parenthesize carefully.

only — hides query from old browsers

@media only screen and (min-width: 768px) { … }

Rarely needed today — only prevents ancient browsers (IE 6–8) from applying styles they can't parse correctly.


5. Media Queries Level 4: range syntax

Modern browsers support a cleaner range syntax that reads more naturally:

Old syntaxNew range syntax
(min-width: 768px)(width >= 768px)
(max-width: 1023px)(width < 1024px)
(min-width: 768px) and (max-width: 1023px)(768px <= width < 1024px)
/* Old */
@media (min-width: 768px) and (max-width: 1023px) {
  .content { padding: 2rem; }
}

/* New — same result, easier to read */
@media (768px <= width < 1024px) {
  .content { padding: 2rem; }
}

Browser support: Supported in Chrome 104+, Firefox 102+, Safari 16.4+. Safe for new projects; add fallbacks if you need older browser support.


6. Practical responsive patterns

Pattern A: stack → side-by-side

.feature-grid {
  display: grid;
  gap: 1.5rem;
}

@media (min-width: 768px) {
  .feature-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1200px) {
  .feature-grid { grid-template-columns: repeat(3, 1fr); }
}

Pattern B: show/hide elements

.mobile-menu-btn { display: block; }
.desktop-nav     { display: none; }

@media (min-width: 768px) {
  .mobile-menu-btn { display: none; }
  .desktop-nav     { display: flex; gap: 2rem; }
}

Pattern C: responsive font sizing

h1 { font-size: 1.75rem; }

@media (min-width: 768px) {
  h1 { font-size: 2.5rem; }
}

@media (min-width: 1200px) {
  h1 { font-size: 3rem; }
}

Pattern D: print-friendly

@media print {
  .no-print { display: none; }
  a[href]::after { content: " (" attr(href) ")"; font-size: 0.8em; }
  body { font-size: 12pt; line-height: 1.4; }
}

7. Where to place media queries

ApproachDescriptionPros / Cons
Bottom of stylesheetAll queries grouped at the endEasy to scan breakpoints; repeated selectors
Near each componentQuery immediately follows the base ruleCo-located, easier to maintain; breakpoints scattered
Separate filesmobile.css, tablet.css, etc.Clean separation; more HTTP requests (mitigated by bundlers)

Most modern projects use the component-adjacent approach — especially with CSS Modules, Sass partials, or utility frameworks.


8. Key takeaways

  1. @media (min-width) is the mobile-first query; max-width is desktop-first.
  2. User preference queries (prefers-color-scheme, prefers-reduced-motion) are accessibility essentials, not nice-to-haves.
  3. hover and pointer let you tailor interactions to the input device.
  4. Use Level 4 range syntax (width >= 768px) in new projects for readability.
  5. Combine with and, , (or), and not — but be precise with not scope.

Explain-It Challenge

Explain without notes:

  1. What is the difference between (hover: hover) and (any-hover: hover), and when would you use each?
  2. Write a media query that applies styles only to tablet-sized screens in portrait orientation, using both old and new (Level 4) syntax.
  3. Why should prefers-reduced-motion: reduce default to removing animation rather than reducing it?

Navigation: ← 1.9.a — Mobile-First Strategy · 1.9.c — Breakpoint Planning →