# Modern Media Queries: Beyond Browser Widths

CSS3 Media Queries are the power behind [Responsive Web Design](https://alistapart.com/article/responsive-web-design/). Rather than building separate sites to target mobile, desktop, and other devices, developers could create a single site which *responded* to the dimensions of the viewport. Layout techniques have evolved and media queries are no longer essential, but this tutorial discusses lesser-known options you could adopt to make your site more resilient and more usable by more people on a wider range of devices.


## What are Media Queries?

You can create responsive designs in a variety of ways, but I favor a mobile-first approach. A simple single-column layout is the default - perhaps with a little padding to stop it touching the edge of the viewport:

```css
main {
  margin-inline: 10px;
}
```

This works on all devices including old browsers which do not support media queries. However, a wide viewport creates long text blocks that are difficult to read. We can introduce a media query to detect when the viewport is equal or greater than 620px and set the content to a 600px centered column:

```css
@media (min-width: 620px) {

  main {
    width: 600px;
    margin-inline: auto;
  }

}
```

You can style any elements within the media query block. For example, you could switch to a multi-column layout, reposition elements, change colors, use a different logo, etc.


## Media Query `<link>` Syntax

The example above presumes you're using a stylesheet loaded by all devices. An alternative option is to split your stylesheets into device-specific styles and load them in an HTML `<link>` tag using a `media` attribute:

```html
<link rel="stylesheet" media="all" href="core.css" />
<link rel="stylesheet" media="(min-width: 620px)" href="wide.css" />
```

All browsers load `core.css`. The `wide.css` stylesheet is only loaded when a device has a viewport width of at least 620px (there's no need to specify the `@media` block within it). This can have a performance benefit on sites with large stylesheets because devices can download the styles they require and no others.


## Mixing Media Queries

As well as viewport dimensions, you can use different media *types* and *features* in expressions as [described below](#modern-media-query-types-and-features). For example `screen`, `print`, and `all` target types of device.

You can combine media queries for more specific styling using `and`, `or`, and `not`. For example, target a screen viewport width between 600px and 1000px:

```css
@media screen and (min-width: 600px) and (max-width: 1000px) {
  /* ... */
}
```

Target any device which is **not** a printer (a `screen`!):

```css
@media not print {
  /* ... */
}
```

Target any device that has a monochrome display or a viewport width of 600px or less:

```css
@media monochrome or (max-width: 600px) {
  /* ... */
}
```


## Modern Range Syntax

The `min-width`, `max-width`, `min-height`, and `max-height` viewport conditions can be confusing and it's easy to get them the wrong way around. Most modern browsers support a newer range syntax, so:

```css
@media (min-width: 600px) and (max-width: 1000px) {
  /* ... */
}
```

becomes:

```css
@media (600px <= width <= 1000px) {
  /* ... */
}
```


## Programmatic Media Queries

You can use media queries to programmatically react to device types, features, or events in JavaScript. You're unlikely to need the [matchMedia API](https://developer.mozilla.org/docs/Web/API/Window/matchMedia) often, but it may be useful for advanced functionality.

The following code detects whether the screen has width of 600px or more. It checks the state on page load and triggers an event when it changes -- perhaps when a user rotates their screen or resizes their browser:

```js
if (matchMedia) {

  // CSS media query
  const mq = window.matchMedia("(min-width: 600px)");

  // device state change event
  mq.addListener(mqChange);

  // detect initial state
  mqChange(mq);
}

// media query state handler
function mqChange(mq) {

  if (mq.matches) {
    // viewport width is >= 600px
  }
  else {
    // viewport width is < 600px
  }

}
```


## The Downsides of Media Queries

Media queries raise some technical difficulties:

1. Code can become verbose and difficult to manage when defining complex sets of breakpoints.

1. Users can zoom or change font size. Browsers handle this differently, so it's best to use `px` viewport dimensions for consistency. That can be limiting.

1. It's not possible to use custom properties (CSS variables) in media queries -- `@media (min-width: var(--my-breakpoint))` will not work.

1. Media queries have become less necessary as CSS has evolved.

Consider our example above which prevents content widths exceeding 600px:

```css
main {
  margin-inline: 10px;
}

@media (min-width: 620px) {

  main {
    width: 600px;
    margin-inline: auto;
  }

}
```

We can replicate the layout in less code without any media query:

```css
main {
  width: calc(100% - 20px);
  max-width: 600px;
  margin-inline: auto;
}
```

or even:

```css
main {
  max-width: min(100% - 20px, 600px);
  margin-inline: auto;
}
```

We can improve it further to use editable custom properties that set margins proportional to the viewport and text widths proportional to the font size -- which is also determined by the viewport width!

```css
:root {
  --margin: 2vw;    /* 2% of viewport width */
  --maxwidth: 75ch; /* 75 characters (approx) */
}

body {
  font-size: clamp(1rem, 0.5rem + 1vw, 3rem);
}

main {
  max-width: min((100% - 2 * var(--margin)), var(--maxwidth));
  margin-inline: auto;
}
```

Media query dimensions are also viewport based, but you often care more about the width or height of the containing element. Most browsers have supported [container queries](https://developer.mozilla.org/docs/Web/CSS/CSS_containment/Container_queries) since 2022. They use a similar syntax to media queries; for example apply `padding` of `2em` to a child element when its parent container has a width of at least `20em`:

```css
.container {
  container: outer / inline-size;
}

.child { padding: 1em; }

@container outer (min-width: 20em) {

  .child { padding: 2em; }

}
```

These techniques are beyond the scope of this article but, taking them to their logical extreme, it becomes less necessary to define viewport-based media queries or fixed breakpoints. Adaptive layouts define constraints so content can expand or contract to fill the available space. You can achieve this without media queries using modern CSS features such as [flexbox](https://developer.mozilla.org/docs/Learn/CSS/CSS_layout/Flexbox), [grid](https://developer.mozilla.org/docs/Web/CSS/grid), [columns](https://developer.mozilla.org/docs/Web/CSS/columns), [intrinsic sizes](https://developer.mozilla.org/docs/Glossary/Intrinsic_Size), `clamp()`, `min()`, `max()`, `fit-content`, `auto-fit`, `minmax`, `gap`, and more.


## Modern Media Query Types and Features

Does this mean media queries are useless? *Absolutely not!*

Media queries support almost 30 device types and features collated into general types below. Some are for niche situations, but others offer greater opportunities for your web site or app.

Note that browser support is good unless indicated otherwise.


### Device Type

You can target specific devices by their general type or display capabilities. These are most often used to provide [printer-friendly web pages](https://blog.openreplay.com/make-your-web-pages-web-printer-friendly-with-css/) but you can also consider targeting ebook readers or even text-based terminals!

The most basic tests are:

* `all` for any device
* `screen` for devices with a screen, and
* `print` for printers and print preview mode

[`monochrome`](https://developer.mozilla.org/docs/Web/CSS/@media/monochrome) determines whether the screen is monochrome such as those found on e-ink displays and ebook readers.

[`color`](https://developer.mozilla.org/docs/Web/CSS/CSS_media_queries/Using_media_queries) tests the number of bits per color component (red, green, blue). Most color devices provide 8 bits per channel -- 24 bits in total -- but older devices may have limitations.

Examples:

```css
/* default black */
body { color: black; }

/* any monochrome screen */
@media screen and (monochrome) {
  body { color: #222; }
}

/* any color screen */
@media screen and (color) {
  body { color: #123; }
}

/* color screen with at least 8 bits for R, G and B (24-bit color) */
@media screen and (min-color: 8) {
  body { color: #123456; }
}

/* color printer */
@media print and (color) {
  body { color: #123456; }
}

/* black and white printer */
@media print and (monochrome) {
  body { color: #222; }
}
```

[`resolution`, `min-resolution`, and `max-resolution`](https://developer.mozilla.org/docs/Web/CSS/@media/resolution) test the screen's pixel density. Most recent phones and tablets can have high native resolutions, known as HiDPI or Retina displays. Each pixel may be invisible to the naked eye, so the browser implements a *CSS resolution* such as 360 x 760px. If the native resolution is 1440 x 3040px, the screen has a display density of 4x. In other words, a single CSS pixel uses 4x4 (16) physical pixels.

You may have used responsive images in HTML `<img>` tags, e.g. use `img100.jpg` on standard displays but replace it with `img200.jpg` on a high-density display of at least 2x (4 pixels per CSS pixel):

```html
<img width="100" height="100"
  alt="responsive image"
  src="img100.jpg"
  srcset="img100.jpg 1x,img200.jpg 2x" />
```

A similar option is available in CSS using the [`image-set()`](https://developer.mozilla.org/docs/Web/CSS/image/image-set) property:

```css
.myimage {
  background-image: image-set("img100.jpg" 1x, "img200.jpg" 2x);
}
```

but you can use a media query if you prefer:

```css
.myimage {
  background-image: url("img100.jpg");
}

@media (min-resolution: 2x) {
  .myimage { background-image: url("img200.jpg"); }
}
```

More advanced but less-used device testing options include:

* [`color-gamut`](https://developer.mozilla.org/docs/Web/CSS/@media/color-gamut) to test the approximate *range* of color (`srgb`, `p3`, or `rec2020`).

* [`color-index` and `min-color-index`](https://developer.mozilla.org/docs/Web/CSS/@media/color-index) to test the number of colors

* [`dynamic-range`](https://developer.mozilla.org/docs/Web/CSS/@media/dynamic-range) to test the brightness, contrast ratio, and color depth (`standard` or `high`)

* [`video-dynamic-range`](https://developer.mozilla.org/docs/Web/CSS/@media/video-dynamic-range) to test a combination of brightness, contrast ratio, and color depth. It can be `standard` for most visual devices or `high` for top-end displays.

* [`grid`](https://developer.mozilla.org/docs/Web/CSS/@media/grid) to test whether the device is a grid-based screen such as a text-only terminal.

* [`scan`](https://developer.mozilla.org/docs/Web/CSS/@media/scan) to test for `progressive` rendering as used on modern devices or `interlace` rendering as used by older CRT monitors and televisions.

* [`update`](https://developer.mozilla.org/docs/Web/CSS/@media/update) to test for the re-rendering frequency. It can be `none` when a layout cannot update (a printed page), `slow` (typically an e-ink display), or `fast` (most phones, tablets, and monitors).


### Layout

Media queries are most often used for responsive designs which test the device [width](https://developer.mozilla.org/docs/Web/CSS/@media/width) as shown above. You can use `width` to test for an exact dimension:

```css
@media (width: 360px) {
  /* ... */
}
```

More realistically, you'll use `min-width`, `max-width`, or operators to test for a range:

```css
@media (600px <= width <= 1000px) {
  /* ... */
}
```

Fewer sites consider [`height`, `min-height`, and `max-height`](https://developer.mozilla.org/docs/Web/CSS/@media/height) but they have uses. In this example, the viewport height determines the site `<header>` layout:

* when the viewport height is less than 1000px, the header scrolls up and out of the viewport as you scroll the content
* when the viewport height is 1000px or more, the header becomes larger and fixes itself to the top of the viewport so it's always available no matter where you scroll.

```css
/* smaller device default */
header {
  width: 100%;
  height: 8vh;
}

/* taller device */
@media (1000px < height) {

  /* adjust header */
  header {
    position: fixed;
    height: 16vh;
    top: 0;
    left: 0;
  }

  /* push content below fixed header */
  main {
    padding-top: 18vh;
    scroll-margin-block-start: 18vh;
  }
}
```

(Note: the `vh` unit is 1% of the viewport's height. The header height will increase accordingly, so you could consider using `clamp()` to specify minimum and maximum dimensions.)

[`orientation`](https://developer.mozilla.org/docs/Web/CSS/@media/orientation) tests whether the device is in `portrait` or `landscape` view. It's probably not something you'll use often because `width` and `height` are more critical, but you could use it for to change the logical direction of content, e.g.

```css
/* default portrait view */
main {
  display: flex;
  flex-direction: column;
}

/* landscape view */
@media (orientation: landscape) {
  main { flex-direction: row; }
}
```

[`aspect-ratio`, `min-aspect-ratio`, and `max-aspect-ratio`](https://developer.mozilla.org/docs/Web/CSS/@media/aspect-ratio) provide more flexibility because you can specify how the width compares with the height. It can be complex, but the following media query triggers when the device is in landscape mode - even when the width is just 1px more than the height:

```css
@media (min-aspect-ratio: 1/1) {
  /* landscape */
}
```

Newer, less-used layout options include:

* [`overflow-inline`](https://developer.mozilla.org/docs/Web/CSS/@media/overflow-inline) determines how the device handles overflows along the inline axis (top to bottom in western cultures). Values can be `none` (overflowing content hidden), or `scroll` (you can scroll to overflowing content).

* [`overflow-block`](https://developer.mozilla.org/docs/Web/CSS/@media/overflow-block) similar, but for the block axis (left to right in western cultures). As well as `none` and `scroll`, it can return printer-based values of `paged` (content splits into pages), or `optional-paged` (page breaks can be manaully triggered).


### Pointer Input

A device can have any number of pointing devices: mouse, trackpad, trackball, pen, touch screen, etc. The browser makes an assumption about which is the primary input type. The following media queries are available:

* [`hover`](https://developer.mozilla.org/docs/Web/CSS/@media/hover) tests whether the user's primary input device can hover over an element (`hover`) or not (`none`). Generally, you can hover with a mouse or track pad, but not with a touch screen or pen.

* [`any-hover`](https://developer.mozilla.org/docs/Web/CSS/@media/any-hover) is similar, but tests whether any available input device could hover.

* [`pointer`](https://developer.mozilla.org/docs/Web/CSS/@media/any-pointer) tests the accuracy of a pointing device: `none` for no primary pointing device, `fine` for accurate devices such as a mouse, and `course` for less accurate devices such as a touch screen.

* [`any-pointer`](https://developer.mozilla.org/docs/Web/CSS/@media/any-pointer) is similar, but tests whether any available input device supports `none`, `fine`, or `course`.

It can be difficult to activate a button or a link on a touch screen if it's less than 7mm width and/or height. You could test for touch screen devices and alter the click area accordingly:

```css
.closebutton {
  width: 20px;
  height: 20px;
  padding: 0;
}

@media (hover: none) or (pointer: coarse) {
  .closebutton { padding: min(0px, 7mm - 20px); }
}
```

That said, it's better to ensure your interactive elements are reasonably large on all devices. Not every mouse or pen user has good motor abilities to click small controls.


### Accessibility and User Preferences

Sites often provide light (black on white) and dark (white on black) theming options. CSS can determine the OS theme preference using [`prefers-color-scheme`](https://developer.mozilla.org/docs/Web/CSS/@media/prefers-color-scheme):

```css
/* default light theme */
body {
  color: black;
  background-color: white;
}

/* dark theme */
@media (prefers-color-scheme: dark) {
  body {
    color: white;
    background-color: black;
  }
}
```

It's often more practical to define a range of color custom properties so they're available throughout your stylesheets. You may also want to offer an override switch so the user can choose a specific theme irrespective of their OS choice.

You should also consider the new [`color-scheme`](https://developer.mozilla.org/docs/Web/CSS/color-scheme) property which can enable rudimentary theming in one line of CSS without media queries!

```css
:root { color-scheme: light dark; }
```

[`prefers-reduced-motion`](https://developer.mozilla.org/docs/Web/CSS/@media/prefers-reduced-motion) tests whether the user chosen to minimize non-essential animations in their OS. These effects can be problematic for those with vestibular motion disorders, so you can test for `reduce` or `no-preference`. The following snippet overrides all transitions, animations, and scroll behaviours to make effects occur instantly:

```css
@media (prefers-reduced-motion: reduce) {

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

}
```

A similar media query is [`prefers-contrast`](https://developer.mozilla.org/docs/Web/CSS/@media/prefers-contrast) which tests the user's contrast preferences. Values can be `no-preference`, `more`, `less`, or `custom` (the user has specified a limited color palette). You could combine this with `prefers-color-scheme` to specify color sets or use the new [`color-mix()` function](https://developer.mozilla.org/docs/Web/CSS/color_value/color-mix) to automatically generate color sets.

More advanced but less-used accessibility options include:

* [`forced-colors`](https://developer.mozilla.org/docs/Web/CSS/@media/forced-colors) to test for limited color palettes such as Windows High Contast Mode (`active`)

* [`inverted-colors`](https://developer.mozilla.org/docs/Web/CSS/@media/inverted-colors) to test if the OS has inverted the colors


### Scripting

[`scripting`](https://developer.mozilla.org/docs/Web/CSS/@media/scripting) tests whether JavaScript is available. The values can be:

* `none` - scripting is not available
* `enabled` - scripting is supported and enabled, or
* `initial-only` - scripting is enabled during the initial page load, but not afterwards

In the past, developers would often add a `js-enabled` or similar class to the `<html>` element using JavaScript when it first runs:

```html
<script type="module">
document.documentElement.classList.add('js-enabled');
</script>
```

This allowed you to alter or hide JS-powered widgets until scripting activated, e.g.

```css
.widget { display: none; }
.js-enabled .widget { display: block; }
```

This is no longer necessary and you can check in CSS alone:

```css
.widget { display: none; }

@media (scripting: enabled) {
  .widget { display: block; }
}
```

You could choose to show a loading spinner or a skeleton layout *before* any JavaScript loads and runs.

Note that [Web Components](https://blog.openreplay.com/an-introduction-to-native-web-components/) can use the [`:defined`](https://developer.mozilla.org/docs/Web/CSS/:defined) pseudo class to hide any element that has not initialized. You could use this in conjunction with a media query, e.g.

```css
/* hide component by default */
my-component:not(:defined) {
  display: none;
}

@media not (scripting: none) {

  /* default styles */
  my-component {
    width: 10em;
    height: 20em;
  }

  /* display component skeleton before initialization */
  my-component:not(:defined) {
    display: block;
    background-color: #ddd;
  }

  /* component initialized */
  my-component:defined {
    display: block;
    background-color: #9ff;
  }

}
```


### Screen Modes

[`display-mode`](https://developer.mozilla.org/docs/Web/CSS/@media/display-mode) tests whether a web site or application is running in a normal browser tab or an alternative way. This is most used in [Progressive Web Apps](https://blog.openreplay.com/upgrade-your-static-website-to-a-pwa/) and the values can be:

* `browser` - the app is running in a normal browser tab or window
* `fullscreen` - the app is running full screen via the [Fullscreen API](https://developer.mozilla.org/docs/Web/API/Fullscreen_API) or the PWA manifest (note that pressing F11 or similar will not necessarily trigger this value).
* `minimal-ui` - the app is running in its own window with minimal navigation controls
* `standalone` - the app is running in its own window with no navigation controls
* `window-controls-overlay` - the app is running in its own window with no navigation controls and the [Window Controls Overlay API](https://developer.mozilla.org/docs/Web/API/Window_Controls_Overlay_API) is controlling the title bar
* `picture-in-picture` - the app is displayed in a floating, always-on-top window triggered by the [Picture-in-Picture API](https://developer.mozilla.org/docs/Web/API/Document_Picture-in-Picture_API).


### Performance

[`prefers-reduced-data`](https://developer.mozilla.org/docs/Web/CSS/@media/prefers-reduced-data) tests whether the user has requested content which consumes less bandwidth -- perhaps because they are on a slower, more limited, or expensive network. [Support is limited to Chrome-based browsers](https://caniuse.com/mdn-css_at-rules_media_prefers-reduced-data) and it must be specifically enabled. The values can be `no-preference` or `reduce`, e.g.

```css
body {
  background-image: url("low-res.jpg");
}

@media (prefers-reduced-data: no-preference) {
  body {
    background-image: url("high-res.jpg");
  }
}
```

Note this would cause `low-res.jpg` to load by default on non-supporting browsers. That may not be your preferred choice.


## Summary

You're unlikely to use all the media query options above, but there's no reason to limit yourself to `min-width` and `max-width`. I recommend you consider:

* `min-height` and `max-height` for improved layout options
* `screen` and `print` for printer-friendly designs
* `monochrome` and `color` for print and e-ink output
* `hover` and `pointer` for standard and touch screen input
* `prefers-color-scheme` and `prefers-reduced-motion` for accessibility
* `scripting` for JavaScript and no-script support
* container queries

CSS media queries are no longer essential for responsive web design, but they remain a viable option for adding supplemental functionality, better resilience, and improved usability on a wider range of devices. They allow your web sites and apps to reach wider audiences.

