Responsive design was introduced more than 10 years ago. In fact, Ethan Marcotte’s seminal article turned 10 on 25 May. Here’s how I would describe responsive design:

The way a website adapts to different screen sizes by reflowing and repositioning content as the available space allows.

We do this by using media queries and flexibly sized elements using relative, minimum and maximum sizes. In recent years, this has gotten easier with the introduction of the Flexbox and Grid layout modes, which have been developed with responsive design in mind. 

Before responsive design, we already had a way to develop websites for smaller devices. Back in 2008 I wrote an article for a Dutch web development site on handheld stylesheets. These used the now deprecated “handheld” media type and were supported by mobile browsers back then, like Opera Mini and Internet Explorer Mobile.

However, overall support for these handheld stylesheets, and what you could do with them, was pretty terrible. I ended my article with a glimmer of hope. Soon we would be able to use a thing called “media queries”, which would allow us to respond to device widths, heights and number of colours. Somehow that seemed important to mention back then. 

The best news was that Opera 9 and Safari 3 already supported it. We didn’t realise it back then, but the mobile internet and media queries were here to stay.

We’re now more than a decade in, and of course support for media queries is terrific:

Support for media queries according to

I also think we have a solid, shared understanding of what constitutes good responsive design. Here’s how I would describe it. (If you disagree, find me on Twitter.)

Responsive design means… 

  • Mobile first
  • Media queries with widths in ems
  • Base font-size of at least 16 pixels
  • Allows for user resizing and zooming, even on mobile
  • Scrolling is fine, because there is no fold.

But I think we’re now at a point where we can start considering a new type of responsiveness. One that doesn’t just look at the width and height of the screen, but also at what your user prefers. For simplicity’s sake, let’s call this: responsive websites.

Responsive websites

A responsive website doesn’t just respond to viewport sizes but also adapts to user preferences. In web development, we usually reason about what a device can do, but we really want to be responsive to the user, their environment and their preferences. The device is just the proxy that sits between us and the user.

Luckily, on the modern web we already have a lot of ways to be more responsive. We can respond to:

  • User preferences
  • Environment
  • Network conditions
  • Device

In the rest of this article, we’ll go over these and see what’s possible in each of these categories. Unless explicitly mentioned you can use these features in production right now, with good support in all evergreen browsers.

User preferences

Let’s start with user preferences. You’re probably familiar with the first one: Prefers-color-scheme. Or as you might know it, dark mode support.

@media (prefers-color-scheme: dark) {
/* wants dark mode */
@media (prefers-color-scheme: light) {
/* wants light mode */

view rawprefers-color-scheme.css hosted with ❤ by GitHub

With this setting, users can indicate to your website that they prefer to see a light or dark interface. If there is no explicit preference, your browser will default to light mode.

A website with both a light and dark mode style shown in Polypane.

Some users might be sensitive to bright lights or just prefer darker colours, but others might explicitly choose a light mode for the usually increased contrast. Offering both a light and dark mode can help make your website more accessible to users. 

If you already have a website, there’s a fair chance that it’s in what you would consider “light mode”. If you want to add a dark mode but don’t have a lot of time or budget, then you can add something I like to call “cheap dark mode”:

@media (prefers-color-scheme: dark) {
:root {
background: #111;
filter: invert(1) hue-rotate(180deg);
video {
filter: invert(1) hue-rotate(180deg);

view rawcheap-dark-mode.css hosted with ❤ by GitHub

This isn’t perfect but it will get you dark mode in just a few lines of code. Here’s how it works: First it adds a dark background colour. Then it uses a filter to invert all the colours on the page (the background won’t be affected). 

The invert turns all light colours dark and all dark colours light, but it also switches all colours. Blue becomes orange, green becomes pink and so on. If you want your colours to remain mostly the same, you’ll also have to rotate their hue 180 degrees, so they’re back to normal. This does mean that your colours’ lightness will change. You don’t get to keep your brand colours perfectly, but this is cheap dark mode after all.

There’s another thing we need to do though: With the invert, all our images and videos are now also inverted and they’re being shown as negatives. We want to turn those back to their original colour. We can do that by adding another invert (inverting the invert) and rotating the hue another 180 degrees (getting it back to the original colour).

Prefers reduced motion

@media (prefers-reduced-motion: reduce) {
/* wants reduced motion */
@media (prefers-reduced-motion: no-preference) {
/* doesn’t want reduced motion */

view rawprefers-reduced-motion.css hosted with ❤ by GitHub

With prefers-reduced-motion a user can indicate that they want to see less stuff happening on the screen. Reasons they want to do this can include motion sickness, vestibular disorders or just plainly they don’t want to wait around while your nice animations play. 

If a user has this turned on, it doesn’t mean you can’t show any motion, just that you have to be mindful: use motion only where it helps understanding, and if you do use motion, choose simple motions like a fade. If it doesn’t help understanding, turn it off. For videos, make sure you don’t autoplay them. 

If you’re building a new website, it’s good to think of motion as a progressive enhancement. Develop your website with reduced motion, and add the animations in a prefers-reduced-motion: no-preference media query. That way the default experience is the more accessible one, and users that don’t mind animations get them for free.

For existing websites you can add “cheap” reduced motion support by turning off all animations and transitions, like so:

Make sure you also use the following bit of JavaScript to check if you can autoplay videos:

const video = document.createElement(‘video’);
const canAutoPlay = window.matchMedia(‘(prefers-reduced-motion: no-preference)’).matches;
video.setAttribute(‘autoplay’, canAutoPlay);

view rawprefers-reduced-motion.js hosted with ❤ by GitHub

The previous two media queries have wide support in all evergreen browsers, but the next few user preferences don’t. Consider them a taste of what’s to come.


A website without (left) and with (right) reduced transparency.

@media (prefers-reduced-transparency: reduce) {
/* wants reduced transparency */
@media (prefers-reduced-transparency: no-preference) {
/* doesn’t want reduced transparency */

view rawprefers-reduced-transparency.css hosted with ❤ by GitHub
Users can use this media query to indicate that they prefer seeing text on solid colours, usually due to visual impairments making it hard to read text on busy backgrounds, like images or patterns. This feature can also help people with dyslexia or concentration problems to read your content easier.

Unfortunately, there is no browser support yet.


@media (prefers-contrast: high) { }
@media (prefers-contrast: no-preference) { }
@media (prefers-contrast: low) { }

view rawprefers-contrast.css hosted with ❤ by GitHub

Equally unsupported, prefers-contrast indicates whether a user prefers a high or low contrast interface. (Notice how it’s prefers-contrast and not prefers-reduced-contrast like the previous media queries. This one goes both ways!)

Some visual impairments can make it difficult to make out details or subtle differences in colour, in which case people will prefer a higher contrast. On the other hand, people might be sensitive to harsh, high contrast and prefer low contrast.

Note that wanting less transparency is not the same as wanting more contrast, and these should not be lumped together. They are there for different reasons and can be compensated differently for.


Safari showing a website without (left) and with (right) inverted colours. Source: The A11Y Project.

@media (inverted-colors: none) {
/* colors are normal */
@media (inverted-colors: inverted) {
/* colors are inverted */

view rawinverted-colors.css hosted with ❤ by GitHub

You’ll notice this media query doesn’t start with prefers, and that’s because it indicates that the operating system has already inverted colours. The operating system in this instance is Mac OS, the only OS to implement this setting. 

You might think you’d use this to double-invert your images (like our cheap dark mode from earlier in the article) but Safari, the only browser to support this media query, already does this for you.

You can however, still invert or hue-shift other parts of your website to make sure your icon colours are still correct and brand colours are being followed.

-ms-high-contrast/forced-colors shown without forced colours (left) and with (right). Source:

On the Windows and Edge side, we have the -ms-high-contrast media query. A big difference with inverted-colors is that it’s much more destructive. It will strip out any background images if there’s text over them and force all your background and text colours to use system colours, making everything uniform with the rest of your operating system.

If you’ve been doing web development for a while, you might remember that we could style elements with system colours. That’s no longer possible due to security implications, but in the windows high contrast mode a subset of them is back:

  • windowText: controls the colour of text content.
  • highlightText: controls the colour of selected text.
  • highlight: controls the background colour of selected text.
  • buttonFace: controls the colour of a <button> element’s text.
  • window: controls the colour of the background.
  • The <a> element controls the colour of links.

These colour names are not here for you to pick and choose, high colour mode already overwrites all your text, background and button colours with these values. They’re there for you to use on other elements to make them fit the rest of the site, like custom icons.

Only Edge implements the -ms-high-contast value, the spec equivalent that’s being worked on is called forced-colors, but it has no browser support at the moment.

These last three media queries, prefers-contrastinverted-colors and forced-colors, do not serve the same purpose. Forced-colors very explicitly overwrites your styling to one of the user’s choosing, often with significantly increased contrast. With prefers-contrast it’s your job to increase contrast, but the user would still like to see your design. Lastly, inverted-colors has no such explicit goal but is primarily used to make screens less bright.


Alongside user preferences, there’s also two upcoming media queries that will tell you something about the environment your page is shown in, light-level and environment-blending. Neither are supported by browsers at the moment.


Polypane emulating a dimmed, normal and washed screen side by side.

Light level has three possible values: dimnormal and washed. When each of these values is triggered is determined by the operating system based on the light sensor or camera on the device.

Light level will be dim if the screen is shown in a dark place, whereas washed means that it’s shown under bright lights or in outdoor conditions with lots of sunlight.

Operating systems nowadays already compensate for these situations by themselves by increasing or decreasing brightness, but we can make specific changes to our web pages too. In dim situations, you might opt to decrease strong contrast here and there and decrease the overall brightness of your page. In washed situations you will want to increase the contrast of all text compared to the background to make sure that it’s still readable.


@media (environment-blending: opaque) { }
@media (environment-blending: additive) { }
@media (environment-blending: subtractive) { }

view rawenvironment-blending.css hosted with ❤ by GitHub

Environment blending is a little more sci-fi. With it you can test whether or not a screen blends with its environment, for example if it’s projected on a piece of glass (think Minority report or the HoloLens). There’s three possible values: opaque, which is the default and like a regular monitor. Additive, which is when the image is projected onto a transparent screen, means that white is 100 percent light and black is 100 percent transparent. Lasty, subtractive is when you have an LCD display like a gameboy screen, where pixels that are “on” are black, and if they’re off, they’re transparent. 

Network: prefers-reduced-data and the save-data header

Not everyone is lucky enough to have fast or reliable internet, or unlimited data plans. If you don’t, you’re generally not happy with websites downloading many megabytes per page and would rather have a slimmed-down version.

Browsers can send the Save-data: on HTTP header, and servers can then choose to send smaller images and videos, prevent downloading some scripts and disable any form of polling or preloading. Mobile browsers have this in their settings already, and for desktop browsers there are browser extensions that do this.

Dealing with such a request on a web server level is often hard to do. Either because you lack access or the configuration requirements to make it work are just too complex. That’s unfortunate because it’s potentially very impactful.

Coming up in browsers, and available behind a flag in Chromium Canary, is the prefers-reduced-data media query. Though you can do less with it compared to the Save-data header (which you could theoretically use to send an entirely different web page), you can still use it in CSS to prevent downloading unneeded fonts and background images, or simply requesting smaller background images. And in JavaScript you can use it to prevent polling, preloading and automatically streaming video.

Though the prefers-reduced-data media query is not implemented yet, both Firefox (behind a flag) and Chrome implement a new Network Information API which lets you check if saveData is on, and what the effective type of an internet connection is.

effectiveType takes into account not just the type (Wi-Fi, cellular etc) but also how long previous round trips to the server took and what the download speed is. There’s slow-2g, 2g, 3g and 4g as possible values.

Responding to device settings

Though my main point in this article is that the device is a proxy for user preferences, we’re also getting more access to information about the device that we can use to provide better experiences.

A notched phone showing bands to prevent text from running behind the notch. Source: Webkit.

<meta name=”viewport” content=”initial-scale=1, viewport-fit=cover” />

view rawviewport-cover.html hosted with ❤ by GitHub

When Apple came out with the notch on the iPhone X, they introduced a new viewport property called viewport-fit: cover, without which a site would be shown banded on a notched phone.

With viewport-fit: cover, text falls behind the notch and becomes unreadable. Source: Webkit.

Turning it on can obscure content, so in order to then respond to that, they also introduced new “environment variables” to use in your CSS to make sure your content would be readable.

body {
env(save-area-inset-top, 0px)
env(save-area-inset-right, 0px)
env(save-area-inset-bottom, 0px)
env(save-area-inset-left, 0px)

view rawenvs.css hosted with ❤ by GitHub

The safe-area-inset-* environment values give you a safe space where your content won’t be obscured. Source: Webkit.

This feature has been available in Mobile Safari since the iPhone 10, and in Chrome for Android since version 69.


Lastly, I want to point out the new interaction media queries. These are supported across the board in evergreen browsers, though iPads don’t change from coarse to fine with the new trackpad (they might in a future update). Nowadays, we have many more input devices compared to when the web got started. Mouse pointers still exist, but we also have touch, external controllers like Wii remotes and even things like AR hand detection.

Some things that are easy to do with a mouse are harder or impossible to do with touch devices, like hitting small targets or hovering. With the interaction media features you can adapt to these devices in clever ways.

@media (hover: none) and (pointer: coarse) {
/* you’re on a touch-only device */
@media (hover: none) and (pointer: fine) {
/* you’re on a device without hover but with a stylus
or other fine pointing device */
@media (hover: hover) and (pointer: coarse) {
/* you’re on a device with hover but a coarse pointer,
like a gamepad or wii remote*/
@media (hover: hover) and (pointer: fine) {
/* you’re on a device with a mouse or trackpad */

view rawinteraction.css hosted with ❤ by GitHub

The way I would go about this is consider a touch device as the most minimal implementation. It won’t have hover effects, and the precision of your input device is coarse. For this group, you can’t have things popping up on hover, so they need to be visible or behind an explicit toggle, and your tap targets will need to be larger. Then as you start to get hover capabilities, you can add those with hover:hover. As you get more precise input, you can choose to make clickable targets smaller (but not too small!). This way you can make an interface that works well with the input device a user has, not despite it.

The interaction media queries pointer and hover will check the primary input device, like touch on mobile, making the pointer coarse even if you’re using a stylus. If you want to check the capabilities of any of the input devices that are connected to your device, you can use any-pointer and any-hover.

In closing

As we’ve seen, there are many more ways to adapt our websites than just to the screen size of your user’s device. With new media queries we can accomodate a user’s preference for a dark website and for seeing less motion. We can serve up a stripped down website if a user wants to save data and make sure our site works regardless of the user’s input device. In the future, we can further adapt our websites to accommodate people’s eyesight, whether they want more or less contrast, or if they need extra help to make text easier to read. We can do this not just based on user preference but also on their environmental conditions, for example when they’re in a dark room or in bright sunlight.

It might feel like a lot more work and a lot more you need to take into account, but remember that was the case with responsive design, too. With responsive design we needed to go from a single preferred screen width to a near-infinite number of screen sizes. And just like with responsive design, we’ll find ways to integrate this into our workflow using progressive enhancement and new defaults. Websites are about to become a lot more responsive, and I’m glad they are.

%d bloggers like this: