The complete guide to CSS media queries

Last summer Kilian Valkhof did a wonderful write-up on the Polypane blog covering CSS Media Queries.

Media queries are what make modern responsive design possible. With them you can set different styling based on things like a users screen size, device capabilities or user preferences. But how do they work, which ones are there and which ones should you use?

Good for beginners, but seasoned developers might also want to take a look at the “New notations in Media query levels 4 and 5” section 😉

The complete guide to CSS media queries →

Nested Media Queries

I can’t seem to find any mention of this in the Media Queries Module specification, but apparently it’s allowed to nest media queries, as shared by Šime Vidas:

That’s … awesome! 🤯

Fiddling with it a bit more, turns out this snippet also works as expected:

@media not print {
  @media (min-width: 0) {
    p {
      font-weight: bold;
    @media (max-width: 750px) {
      p {
        background: yellow;

You can play with this CodePen demo to try it yourself.

💁‍♂️ Don’t confuse Nested Media Queries with CSS Nesting, an upcoming feature of CSS, which allows you to nest selectors.

UPDATE: Thanks to reader Vadim Makeev for pointing out that support for nested @media blocks was added to Opera 12.50 back in 2012! Its syntax is defined in the CSS Conditional Rules Module specification.

🔥 Like what you see? Want to stay in the loop? Here's how:

Creating websites with prefers-reduced-data

Part of CSS Media Queries Level 5 is the User Preference Media Feature prefers-reduced-data:

The prefers-reduced-data CSS media feature is used to detect if the user has requested the web content that consumes less internet traffic.

There currently is no browser support at all, but that doesn’t stop Kilian Valkhof from taking a peek under the hood.

/* Serve a smaller image by default */
body {
  background-image: url(/images/small-fast-image.webp);

/* But replace it with a large image when applicable */
@media (prefers-reduced-data: no-preference) {
  body {
    background-image: url(/image/large-pretty-image.png);

Like prefers-reduced-motion, it’s good to think of the prefers-reduced-data: reduce option as the default option: people get the lean, fast experience, and only when they indicate no-preference, we send them more data. That way, older browser[s] that don’t support the media query get the lean experience by default.

I especially like the example where the Infinite Scroll paradigm is replaced with a “load more” button when the user has prefers-reduced-data: reduce set. Clever!

Creating websites with prefers-reduced-data

🔥 Another great User Preference Media Feature is prefers-reduced-motion, which can easily be taken into account by using CSS Custom Properties. And you most likely will have heard about prefers-color-scheme too, which allows you to implement Dark Mode.

Container Queries are coming to Chromium!

Update 2021-02-12: Thanks to the hard work by Miriam Suzanne and others this proposal is now officially part of the CSS Specification Process (ref) and set to be part of css-contain-3 … it’s happening, people! 🎉

Update 2021-03-28: A first iteration of this implementation has landed in Chrome 91 … let’s take a closer look and build a demo!

Just announced on the Chromium mailing list is an “Intent to Prototype” Container Queries, which is quite exciting news I must say!

🤔 Container Queries?

Container Queries allow authors to style elements according to the size of a container. This is similar to a @media query, except that it evaluates against a container instead of the viewport.

The experimental implementation will follow Miriam Suzanne’s proposal, which looks like this:

/* (1) Create an implicit "container root" */
aside {
  contain: size;

.media-object {
  display: grid;
  gap: 1em;

/* (2) Container Query targeting the nearest 
   "container root" ancestor. The rules nested
   inside will only be applied if the "container
   root" has a max-width of 45em */
@container (max-width: 45em) {
  .media-object {
    grid-template: 'img content' auto / auto 1fr;

Applying contain: size; (1) onto an element will make it an implicit “container root” or “containment context”. Elements contained inside it can then have container queries applied onto them, by use of a new at-rule @container (<container-media-query>) (2). The target selector and CSS rules to apply in that case are — similar to what we do with “regular” media queries — nested within the @container at-rule.

In the example above extra rules will be applied to .media-object whenever its nearest “container root” ancestor — such as <main> or <aside> — has a max-width of 45em.

🧑‍🔬 This proposal is experimental and has not been approved by the CSSWG yet. The expressed “intent to prototype” is meant as an experiment to see whether this idea would be worth pursuing or not. In the end, it could be that the final syntax can differ from the one listed here, if the proposal is workable in the first place.


A previous version of this proposal by L. David Baron required a context selector to be set, but that has been dropped here. The @container rule from Miriam’s version will work in any containment context (read: the nearest parent element that has contain: size set). The syntax might still change, but that’s irrelevant to the prototype which is to be implemented:

This is not at all finalized, but the underlying problems we need to solve in Blink are (mostly) the same regardless of how the feature is accessed, so we’ll for now use this proposal as the temporary syntax.


Intent to Prototype: Container Queries →
Chrome Tracking Bug →

🔥 Like what you see? Want to stay in the loop? Here's how:

New media queries you need to know

Apart from the quite well known prefers-color-scheme and prefers-reduced-motion features, the Media Query Level 5 spec comes with a few more new ones.

The Media Query Level 5 spec is being drafted as we speak, and it includes some really nice ones. Some of them are still being figured out, but several of them are available in some browsers.

This article will take you through some of the most interesting new media queries in this new specification, and show you how to use them.

The light-level feature – which is used to query about the ambient light-level in which the device is used – looks like a very welcome addition 🙂

New media queries you need to know →

Take both Light and Dark Mode screenshots with Puppeteer

dark-mode-screenshot is a Puppeteer script to take screenshots of both the light and dark mode versions of a website.

$ npx dark-mode-screenshot --url --output screenshot --fullPage

Works in somewhat odd way first requiring the OS to have dark mode enabled (?), and then launch Chromium:

  1. Once with prefers-color-scheme disabled (using --disable-blink-features=MediaQueryPrefersColorScheme)
  2. Once with Dark Mode force enabled (using --force-dark-mode)

dark-mode-screenshot (GitHub) →

❓ New to Dark Mode? No worries, this post on CSS Color Scheme Queries has got you covered.

CSS Color Scheme Queries (“Dark Mode CSS”)

Next to Safari 12.1 earlier this month, Firefox 67 now also supports “CSS Color Scheme Queries”.

The prefers-color-scheme media feature allows sites to adapt their styles to match a user’s preference for dark or light color schemes, a choice that’s begun to appear in operating systems like Windows, macOS and Android.

Chrome will support + enable it by default in Chrome 76 (the current Canary build at the time of writing)


Defining a “Dark Mode” for your websites becomes really easy when you combine prefers-color-scheme with CSS Custom Properties (“CSS Variables”):

:root {
    color-scheme: light dark;
    --special-text-color: hsla(60, 100%, 50%, 0.5);
    --border-color: black;

@media (prefers-color-scheme: dark) {
    :root {
        --special-text-color: hsla(60, 50%, 70%, 0.75);
        --border-color: white;

.special {
    color: var(--special-text-color);
    border: 1px solid var(--border-color);


If you’re too lazy, then you can somewhat fake it by abusing mix-blend-mode: difference;, but it’s not perfect. Here’s an adjusted snippet, which injects the hack on the body using ::before:

@media (prefers-color-scheme: dark) {
    body::before {
        content: '';
        display: block;
        width: 100vw;
        height: 100vh;
        position: fixed;
        top: 0;
        left: 0;
        background: white;
        mix-blend-mode: difference;
        z-index: 1;
        pointer-events: none;


A nice touch of Safari is that its DevTools also change when Dark Mode is enabled:

WebKit: Dark Mode Support in WebKit →
WebKit: Dark Mode Support in Web Inspector →Firefox 67: Dark Mode CSS, WebRender, and more →

How to troll frontend web developers

@media only screen and (min-width: 960px) and (max-width: 970px) {
    body {
        -webkit-transform : rotate(180deg);
        -moz-transform : rotate(180deg);
        -o-transform : rotate(180deg);
        transform : rotate(180deg);

That’ll teach them, constantly resizing their browser windows and what not! — Puh!