CSS Media Queries

1

CSS Media Queries

▶

Modern websites are viewed on an enormous range of devices — from small smartphones held in one hand to wide desktop monitors, tablets in both orientations, and even printers. CSS alone, without any conditional logic, would apply the same layout rules to all of them, often producing cramped, overflowing, or comically oversized results. CSS media queries solve this by letting you wrap a block of CSS declarations inside a condition. The browser evaluates that condition against the current environment, and only if the condition is true does it apply the enclosed styles. This single mechanism is the backbone of responsive web design.

A media query is written with the @media at-rule. At its simplest it looks like this:

@media screen and (max-width: 768px) {
  body {
    font-size: 16px;
  }
}

Here the browser asks two questions: Is the output device a screen? Is the viewport width 768 px or less? Only when both answers are yes does it set font-size to 16 px on body. When the viewport is wider — or when the page is being printed — that rule is silently ignored. This conditional scoping is what makes media queries so powerful: every style you write inside a query is invisible to environments that don't match it.

The practical benefit goes far beyond font sizes. With media queries you can restructure entire grid layouts, hide or reveal navigation patterns, swap images, adjust spacing, and even change animation behaviour — all driven by the real characteristics of the user's current environment rather than assumptions baked into the design at build time.

Basic Media Query Syntax

Every media query is built from up to three ingredients: an optional media type, one or more feature expressions, and logical operators that combine them.

The media type is a broad category keyword that says what kind of output device the query targets. The most common values are:

  • screen — any colour screen device (phones, tablets, desktops, laptops).
  • print — the page as it would appear on paper or in a print preview.
  • all — every output type; this is the default when you omit the media type entirely.

A feature expression tests a specific characteristic of the environment, always wrapped in parentheses:

(max-width: 600px)
(orientation: landscape)
(min-resolution: 2dppx)

Logical operators let you build compound conditions:

  • and — every condition must be true. Example: screen and (min-width: 400px) and (max-width: 900px).
  • comma (acts as or) — at least one of the comma-separated queries must match. Example: screen and (max-width: 600px), print applies to narrow screens or print.
  • not — negates the entire query. Example: not print matches everything that is not a printer.

When the media type is omitted, the browser assumes all, so the following two rules are equivalent:

/* Explicit */
@media all and (max-width: 480px) { … }

/* Implicit — media type defaults to 'all' */
@media (max-width: 480px) { … }

This shorthand is extremely common in real codebases. You will encounter it constantly.

Targeting Screen Sizes with Width Breakpoints

Width-based queries are by far the most used type of media query. The viewport width is the single most reliable signal for how much horizontal space a layout has to work with, and most responsive design systems are structured around a set of width thresholds called breakpoints.

There are two fundamental approaches, each anchored to a different feature:

max-width applies its styles when the viewport is at or below the specified value. This is the heart of a desktop-first workflow: you write your full-featured desktop layout as the base, then override parts of it as the screen shrinks.

/* Base styles target a wide desktop layout */
.container {
  display: grid;
  grid-template-columns: 250px 1fr;
  gap: 2rem;
}

/* Override for screens 768 px wide or narrower */
@media (max-width: 768px) {
  .container {
    grid-template-columns: 1fr; /* Stack columns */
    gap: 1rem;
  }
}

min-width applies its styles when the viewport is at or above the specified value. This is the heart of a mobile-first workflow: you write your simplest, lightest layout as the base and progressively enhance it for larger screens.

/* Base styles for narrow, mobile screens */
.container {
  display: block;
  padding: 1rem;
}

/* Enhancement for screens 768 px and wider */
@media (min-width: 768px) {
  .container {
    display: grid;
    grid-template-columns: 250px 1fr;
    padding: 2rem;
  }
}

You can combine both features in a single query to target a specific band of screen widths, leaving smaller and larger screens untouched:

/* Applies only to viewports between 600 px and 1024 px */
@media (min-width: 600px) and (max-width: 1024px) {
  .sidebar {
    width: 200px;
  }
}

A critical best practice is to choose breakpoints based on your content, not on the pixel dimensions of popular device models. Device screen sizes change every product cycle; a breakpoint invented to match a specific phone model becomes outdated within a year or two. Instead, open your design in a browser and slowly drag the viewport narrower until the layout starts to look awkward or broken — that is where a breakpoint genuinely belongs.

A typical mobile-first breakpoint scale might look like this:

Breakpoint name Min-width value Typical target
Base (no query) 0 px Small phones
Small 480 px Large phones / small tablets
Medium 768 px Tablets / large landscape phones
Large 1024 px Small laptops / large tablets
Extra large 1280 px Desktop monitors

Orientation and Aspect Ratio Queries

Beyond raw width, the orientation of the viewport carries design-relevant information. A tablet held upright has very different proportions from the same tablet turned sideways, even if the raw pixel count is identical.

  • orientation: portrait — matches when the viewport's height is greater than or equal to its width. Most phones in a natural upright position match this.
  • orientation: landscape — matches when the viewport's width strictly exceeds its height. A phone rotated sideways, or almost any laptop or desktop, matches this.
/* Tighten horizontal padding in portrait so content isn't pinched */
@media (orientation: portrait) {
  .hero-image {
    height: 50vw;
  }
}

/* Use the extra width in landscape to lay out content side by side */
@media (orientation: landscape) {
  .hero-image {
    height: 35vw;
  }
}

The aspect-ratio feature goes further, letting you target a precise width-to-height ratio. This is especially useful for video players or image galleries where you want to maintain a specific cinematic or widescreen proportion:

/* Target viewports with exactly a 16:9 ratio */
@media (aspect-ratio: 16/9) {
  .video-player {
    /* Perfect fit, no letterboxing needed */
  }
}

/* Target viewports wider than 16:9 */
@media (min-aspect-ratio: 16/9) {
  .video-player {
    max-width: 80vw;
  }
}

An important caveat: orientation alone is a blunt instrument. A landscape phone and a landscape desktop monitor both match orientation: landscape, but they have vastly different amounts of space. For this reason, orientation queries work best as a supplement to width breakpoints, fine-tuning specific components rather than driving the entire layout strategy.

Device Capability Queries

Some of the most powerful media features go beyond the viewport's dimensions and probe the capabilities of the hardware itself. These queries let you tailor the experience to real user hardware rather than making assumptions.

The resolution feature targets display pixel density. Standard screens show one device pixel per CSS pixel; high-density "Retina" or HiDPI displays pack two or more physical pixels into that same CSS pixel. An icon or image that looks sharp on a standard screen can appear blurry on a Retina display unless you serve a higher-resolution asset.

/* Serve a standard icon by default */
.logo {
  background-image: url('logo.png');
}

/* Serve a 2× asset to Retina/HiDPI screens */
@media (min-resolution: 2dppx) {
  .logo {
    background-image: url('logo@2x.png');
    background-size: contain;
  }
}

dppx means "dots per CSS pixel". You may also see the older dpi unit; 2 dppx equals 192 dpi.

The hover feature asks whether the device's primary input can hover over elements without activating them. A mouse can; a finger on a touchscreen cannot.

  • hover: hover — the primary input supports hover (typically a mouse or trackpad).
  • hover: none — the primary input cannot hover (typical of touch-only devices).
/* Only show dropdown on hover when the device supports it */
@media (hover: hover) {
  .nav-item:hover .dropdown {
    display: block;
  }
}

/* On touch devices, use a click-to-open pattern instead (handled in JS) */
@media (hover: none) {
  .dropdown-toggle {
    display: inline-block; /* Make toggle button visible */
  }
}

The pointer feature describes the precision of the primary input device:

  • pointer: fine — a precise pointing device, such as a mouse or stylus, is primary. Small targets are acceptable.
  • pointer: coarse — an imprecise pointing device, such as a finger on a touchscreen, is primary. Targets must be large enough to hit reliably (the W3C recommends at least 44×44 CSS pixels).
  • pointer: none — no pointing device at all (e.g., a keyboard-only interface).
/* Enlarge buttons and form controls for touch devices */
@media (pointer: coarse) {
  button,
  input[type="checkbox"],
  input[type="radio"] {
    min-height: 44px;
    min-width: 44px;
  }
}

These capability queries let you build interfaces that are genuinely adapted to how users interact with them — not just how big their screens are. A large tablet could have a coarse pointer (finger) or a fine one (stylus), so width alone would not tell you how big to make your tap targets.

Applying Media Queries: Inline vs. External

There are two places you can write media queries: embedded in your main stylesheet, or as a condition on a separate <link> element in your HTML.

Inline media queries live inside your CSS file alongside the rest of your rules:

/* styles.css */

.card {
  padding: 1rem;
}

@media (min-width: 768px) {
  .card {
    padding: 2rem;
  }
}

This approach keeps all styles for a component in one place, which is maintainable and means only one HTTP request for the stylesheet. It is the dominant pattern in modern projects, especially those using bundlers like Webpack or Vite, or preprocessors like Sass or PostCSS.

External media queries use the media attribute on a <link> tag to load an entire separate stylesheet conditionally:

<link rel="stylesheet" href="base.css">
<link rel="stylesheet" href="mobile.css" media="screen and (max-width: 600px)">
<link rel="stylesheet" href="print.css" media="print">

A crucial subtlety: modern browsers download all linked stylesheets regardless of whether the media condition currently matches — they just defer applying the non-matching ones. This means external media queries do not save bandwidth; they only control which styles the browser parses and applies. For printing stylesheets this pattern is common and clean; for responsive breakpoints it is largely obsolete in favour of inline queries.

Within a single stylesheet, you have two organizational options for where to place your media queries:

  • Component-adjacent placement — put each component's responsive overrides immediately after that component's base styles. When you open a component's code you see its full responsive behaviour in one place.
  • Grouped at the bottom — collect all media queries at the end of the file. Some developers prefer this to avoid scrolling past queries while editing base styles.

Neither is definitively correct; consistency within a project matters more than the choice itself.

Best Practices for Writing Media Queries

Years of industry experience have crystallised a set of guidelines that make media queries easier to write, reason about, and maintain over time.

Adopt a mobile-first approach. Write your base CSS for the narrowest viewport — the most constrained environment — and use min-width queries to progressively add complexity for larger screens. This has three concrete benefits: smaller screens (often on slower networks) receive the lightest CSS with no overrides needed; the cascade works with you rather than against you; and you are forced to prioritise essential content from the start.

/* Mobile-first: base is a single-column, minimal layout */
.grid {
  display: block;
}

/* Tablet and up: introduce a two-column grid */
@media (min-width: 768px) {
  .grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
}

/* Desktop and up: three columns */
@media (min-width: 1200px) {
  .grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

Keep the number of breakpoints minimal. Every breakpoint you add must be tested, maintained, and documented. Most projects need no more than three or four. If you find yourself adding breakpoints every 50 px to fix small alignment issues, the root problem is usually that the layout itself lacks flexibility (consider flexbox or grid with auto-fill/auto-fit instead).

Never use device-specific pixel values. Breakpoints like 320 px (original iPhone), 375 px (iPhone 6/7/8), or 1366 px (common laptop) seem specific and precise, but device screen sizes shift with every new product generation. A breakpoint that perfectly targets today's popular phone will be misaligned by next year's model. Let your content dictate the breakpoint: resize the browser until something breaks, then add a breakpoint there.

Test on real devices and multiple browsers. Browser developer tools include excellent device emulators, but they simulate dimensions and pixel density — not always the exact rendering engine behaviour, touch event handling, or font rendering of the actual hardware. Whenever possible, test on a real phone, a real tablet, and at least two different browsers. Pay special attention to:

  • The browser's own UI (address bar, bottom navigation bar) consuming viewport height on mobile.
  • Font rendering differences between operating systems.
  • The hover and pointer media features, which behave differently on hybrid laptop/tablet devices.

Use em or rem units for breakpoints when supporting users who change their browser's default font size. If a user sets their base font to 20 px for readability and your breakpoints are all in px, your layout may break at a point the user's device wasn't actually narrow enough to warrant. Writing breakpoints in em (relative to the browser's font size, not the page's) ensures the layout adapts to both viewport width and the user's text scaling preferences:

/* 768px ÷ 16px default font size = 48em */
@media (min-width: 48em) {
  /* Tablet layout */
}

Together, these practices make media queries a durable, maintainable tool rather than a growing tangle of overrides that becomes harder to manage with every design revision.

NotesCovers all listed subtopics in depth: definition and conditional logic of @media, syntax (media types, feature expressions, logical operators), width breakpoints (max-width desktop-first vs. min-width mobile-first, range queries, content-driven breakpoints), orientation and aspect-ratio queries, device capability queries (resolution, hover, pointer), inline vs. external application, and best practices. Includes code examples for every major concept and one HTML table illustrating a common breakpoint scale.