Skip to main content

🎚️ Media Queries and Breakpoints

If responsive design is the philosophy, media queries are the switchgear. They let one stylesheet ask questions about the device β€” how wide is the viewport? is this a touchscreen? does the user prefer dark mode? β€” and apply different rules based on the answers. This lesson turns that switch on.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Write a valid media query and explain each part of its syntax
  • Use the most common media features β€” width, orientation, pointer, and user-preference queries
  • Choose breakpoints driven by content rather than by specific device sizes
  • Apply the mobile-first (min-width) pattern and know when max-width is appropriate
  • Recognize container queries and @supports, and debug active breakpoints in DevTools

Estimated Time: 30–40 minutes  β€’  Difficulty: Beginner–Intermediate

Hands-on: Build a three-section layout that reflows across mobile, tablet, and desktop breakpoints.

In This Lesson

What Media Queries Do

A media query is a conditional wrapper around a block of CSS. It reads, in plain English: "If the device matches these conditions, apply these rules." Everything outside the query is the baseline that always applies; the query only adds or overrides rules when its condition is true.

graph TD A["@media condition { ... }"] --> B{Condition true?} B -->|Yes| C[Apply the wrapped rules] B -->|No| D[Ignore the block, keep base styles]

πŸ“– Key Terms

Media feature: the device characteristic being tested β€” e.g. width, orientation, pointer.

Breakpoint: a specific viewport width where you switch layouts.

Viewport meta tag: the <meta name="viewport"> line that tells mobile browsers to use the real device width β€” without it, media queries fire against a faked desktop width.

Syntax & Features

A media query is built from an optional media type, one or more feature tests joined by logical operators, and a block of CSS.

@media screen and (min-width: 768px) {
  /* Applies to screens 768px wide and up */
  .container { max-width: 750px; }
}

Common media types

  • all β€” every media type (the default if you omit it)
  • screen β€” monitors, tablets, phones
  • print β€” printers and print preview

Common media features

FeatureTests for
min-width / max-widthViewport width thresholds β€” by far the most used
orientationportrait or landscape
aspect-ratioWidth-to-height ratio of the viewport
resolutionPixel density, for targeting high-DPI (Retina) screens
hoverWhether the device can hover (hover / none)
pointerPointer precision: fine (mouse) or coarse (touch)
prefers-color-schemeUser's light/dark preference
prefers-reduced-motionWhether the user asked to minimize animation

βœ… Modern range syntax

Current browsers support comparison operators, which read more naturally than min-/max- pairs:

/* Old */
@media (min-width: 768px) and (max-width: 1023px) { ... }

/* New range syntax β€” same meaning, clearer intent */
@media (768px <= width < 1024px) { ... }

Worked Examples

Width-based adaptation

/* Base (mobile) styles apply everywhere first */
.sidebar { display: none; }

/* On wider screens, reveal the sidebar */
@media (min-width: 768px) {
  .sidebar { display: block; width: 25%; }
  .content { width: 75%; }
}

Adapting to input type

Touch users need bigger tap targets than mouse users. The pointer feature lets you serve each appropriately.

/* Precise pointer (mouse): compact controls are fine */
@media (pointer: fine) {
  .button { padding: 6px 12px; }
}

/* Coarse pointer (touch): enlarge to a comfortable tap target */
@media (pointer: coarse) {
  .button { padding: 14px 22px; } /* ~44px tall */
}

Print styles

@media print {
  nav, footer, .comments { display: none; }   /* strip screen-only chrome */
  body { font-size: 12pt; color: #000; }
  a[href]::after { content: " (" attr(href) ")"; } /* show URLs on paper */
}

πŸ’‘ The viewport meta tag is not optional

Every responsive page needs this in its <head>, or mobile browsers render at a ~980px virtual width and shrink the result β€” defeating your queries entirely:

<meta name="viewport" content="width=device-width, initial-scale=1.0">

Choosing Breakpoints

A breakpoint is simply the width where you decide the layout should change. There are two schools of thought about where to put them.

Device-based versus content-based breakpoints Device-based breakpoints snap to fixed phone, tablet, and desktop widths; content-based breakpoints are placed wherever the layout starts to look wrong. Device-based (old) 320–480px Β· Phone 768–1024px Β· Tablet 1024px+ Β· Desktop breaks on the next new device Content-based (modern) when text lines get too long when columns feel cramped when proportions look off future-proof, tied to your design
Figure 1 β€” Device-based breakpoints tailor to "small/medium/large"; content-based breakpoints follow where your own layout actually strains.

The content-based approach wins: start from your base design, slowly widen the browser, and add a breakpoint at each point where the layout starts to look stretched, cramped, or awkward. Your breakpoints then belong to your content, not to a device catalog that changes every year.

πŸ’‘ A pragmatic default set

Frameworks like Bootstrap 5 offer sensible starting thresholds β€” ~576px, 768px, 992px, 1200px, 1400px β€” but treat them as a starting grid, then adjust to your content.

Common Patterns

Mobile-first (min-width) β€” the default

Write the simplest mobile layout as the base, then layer enhancements upward. This keeps the base lightweight and is the pattern we recommend throughout this course.

/* Base: stacked navigation on small screens */
.navigation { display: flex; flex-direction: column; }

/* Enhancement: go horizontal on larger screens */
@media (min-width: 768px) {
  .navigation { flex-direction: row; }
}

Desktop-first (max-width) β€” use sparingly

/* Base: full desktop layout */
.navigation { display: flex; flex-direction: row; }

/* Override downward for small screens */
@media (max-width: 767px) {
  .navigation { flex-direction: column; }
}

Multi-column to single-column grid

.grid { display: grid; grid-template-columns: 1fr; }        /* mobile: 1 col */
@media (min-width: 768px) { .grid { grid-template-columns: 1fr 1fr; } }        /* tablet: 2 */
@media (min-width: 992px) { .grid { grid-template-columns: 1fr 1fr 1fr; } }    /* desktop: 3 */

⚠️ Don't over-fragment

Too many breakpoints make CSS brittle and hard to reason about. Prefer intrinsically fluid layouts (Flexbox, Grid auto-fit) and add a query only where fluidity alone can't express the change.

Modern Queries

Container queries

A media query asks about the viewport. A container query asks about a component's own parent β€” so a card can lay itself out differently in a narrow sidebar than in a wide main column, on the same page. This is now supported across all evergreen browsers.

.card-wrapper { container-type: inline-size; }

/* When the WRAPPER (not the screen) is at least 400px wide */
@container (min-width: 400px) {
  .card { display: grid; grid-template-columns: auto 1fr; }
}

Feature queries with @supports

Media queries test the device; @supports tests the browser's CSS capabilities, so you can progressively enhance without breaking older engines.

.layout { display: block; }               /* safe fallback */
@supports (display: grid) {
  .layout {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  }
}

User-preference queries

@media (prefers-color-scheme: dark) {
  body { background: #121212; color: #f0f0f0; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

Debugging

Browser DevTools make breakpoints visible. In the device toolbar (Ctrl/Cmd + Shift + M), a ruler across the top shows your defined breakpoints as clickable segments, and the Styles pane highlights which @media block is currently active.

For quick visual confirmation during development, a temporary label that changes per breakpoint tells you at a glance which query is live:

body::before {
  content: "mobile";
  position: fixed; top: 0; right: 0; z-index: 9999;
  padding: 4px 8px; background: crimson; color: #fff; font: 12px monospace;
}
@media (min-width: 768px) { body::before { content: "tablet";  background: royalblue; } }
@media (min-width: 992px) { body::before { content: "desktop"; background: seagreen;  } }

⚠️ Remove debug helpers before shipping

The label above is a development aid only. Strip it (or guard it behind a dev-only class) before deploying.

Hands-on Exercise

πŸ‹οΈ Build a Reflowing Layout

Objective: Write mobile-first media queries that reflow a page across three breakpoints.

Instructions:

  1. Create a page with a header (logo + nav), a main area holding three content cards, a sidebar, and a footer.
  2. Write the base CSS for mobile: everything stacks in a single column; the nav is hidden behind a toggle.
  3. Add @media (min-width: 768px): cards become a two-column grid and the nav goes horizontal.
  4. Add @media (min-width: 992px): cards become three columns and the sidebar sits beside the main content.
  5. Add one non-width query β€” either a @media print block or a prefers-reduced-motion block.
πŸ’‘ Hint

Lay the main + sidebar out with display: grid on a wrapper. At the desktop breakpoint, switch grid-template-columns from 1fr to something like 3fr 1fr. Keep every query min-width so each one only adds to the mobile base.

βœ… Sample solution
/* Base: mobile, single column */
.layout { display: grid; grid-template-columns: 1fr; gap: 1rem; }
.cards  { display: grid; grid-template-columns: 1fr; gap: 1rem; }
.nav-menu { display: none; }
.nav-toggle { display: block; }

/* Tablet */
@media (min-width: 768px) {
  .cards { grid-template-columns: 1fr 1fr; }
  .nav-menu { display: flex; }
  .nav-toggle { display: none; }
}

/* Desktop */
@media (min-width: 992px) {
  .cards  { grid-template-columns: 1fr 1fr 1fr; }
  .layout { grid-template-columns: 3fr 1fr; } /* main | sidebar */
}

/* Non-width query */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

🎯 Quick Quiz

Question 1: In a mobile-first stylesheet, which query type layers enhancements on top of the base styles?

Question 2: What distinguishes a container query from a media query?

Question 3: Why should breakpoints usually be content-based rather than tied to specific device widths?

Summary & Quiz

πŸŽ‰ Key Takeaways

  • A media query wraps CSS in a condition and applies it only when the condition is true.
  • Beyond width, you can test orientation, pointer, resolution, and user preferences.
  • Prefer content-based breakpoints and the mobile-first min-width pattern.
  • Container queries and @supports extend responsiveness to components and browser capabilities.
  • Always ship the viewport meta tag; use DevTools to watch active breakpoints.

πŸ“š Further Reading

πŸš€ What's Next?

You now control when styles change. Next we make the mobile-first idea a deliberate strategy β€” starting from the smallest screen and progressively enhancing, with the performance and content-prioritization habits that come with it.

πŸŽ‰ Great work!

Media queries are the throttle of responsive design. Next, let's decide which direction to drive.