Responsive and modern CSS

Media queries

Apply styles when viewport or user media features match and choose content-driven breakpoints instead of device labels.

A media query wraps a block of CSS in a condition. When the condition is true, the rules inside apply. When it’s false, the browser skips them.

The most common condition is the viewport width. Here the hero is a single block by default and becomes two columns when there is enough room:

.hero {
  display: block;
}

@media (min-width: 48rem) {
  .hero {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
}

This is the mobile-first approach. The simple layout is the default, with no query around it. The richer layout is the enhancement, and it kicks in only when the viewport is at least 48rem wide. I always write queries this way. Starting from the desktop and undoing things for small screens means more code and more bugs.

Pick breakpoints from the content

A 48rem breakpoint is not there because a spreadsheet says “tablet”. It’s there because that’s roughly where two columns of text stop being readable. The right way to find a breakpoint is to resize the browser until the layout looks cramped, and put the breakpoint there.

Use rem rather than px. In a media query, rem is based on the browser’s initial font size, which includes the user’s preference. Someone who set their default font to 24px hits your breakpoint earlier and gets the single-column layout when they need it. Note that it does not follow a font-size you set on html, only the browser default.

Queries for preferences and capabilities

Width is one media feature among many. You can also ask about the user’s preferences and the device’s input:

@media (prefers-reduced-motion: reduce) { }
@media (prefers-color-scheme: dark) { }
@media (hover: hover) and (pointer: fine) { }
@media print { }

The first two read operating system settings. The third asks whether the primary input can hover and is precise, which describes a mouse or trackpad. The last one applies when the page is printed, handy for hiding navigation.

Don’t guess the device from one feature

A laptop with a touchscreen has both a fine pointer and a coarse one. A phone with a Bluetooth mouse can hover. So never use (hover: hover) to decide whether someone is “on desktop”. Build the essential interaction for every input, then use the query for a real extra, like a hover highlight that touch users never needed anyway.

Both branches must work

A query is correct only when the page is complete on both sides of it. If the two-column hero looks great but the single-column default has no spacing, you’ve only finished half the job.

Try this on the course page: drag the DevTools viewport until the hero looks squeezed, not until the ruler hits a round number. Write down that width and test 20px below and above it. Then emulate reduced motion, dark color scheme, and print from the Rendering panel, and check every branch is usable.

Lesson completed