Tag: Media

Nested Media Queries

Using media queries in CSS as part of responsive websites is bread and butter stuff to todays front-end developer. Using preprocessors to make them more comfortable to write and easier to maintain has become common practice as well.

I spent a few months experimenting with a dozen different approaches to media queries in Sass and actually used a few in production. All of them eventually failed to cater for everything I needed to do in an elegant way. So I took what I liked about each of them and created a solution that covered all scenarios I came across.

Why use a preprocessor at all?

That’s a fair question. After all, what’s the point of doing all this if one can simply write media queries using pure CSS? Tidiness and maintainability.

The most common use for media queries is the transformation of a layout based on the browser’s viewport width. You can make a layout adapt in such a way that multiple devices with different screen sizes can enjoy an optimal experience. As a consequence, the expressions used to define the media queries will make reference to the typical screen width of those devices.

So if your code contains 5 media queries that target tablet devices with a width of 768px, you will hardcode that number 5 times, which is something ugly that my OCD would never forgive. First of all, I want my code to be easy to read to the point that anyone understands instantly that a media query is targeting tablet devices just by looking at it – I reckon the word tablet would do that better than 768px.

Also, what if that reference width changes in the future? I hate the idea of replacing it in 5 instances around the code, especially when it’s scattered around multiple files.

A first step would be to store that breakpoint in a variable and use it to construct the media query.

/* Using plain CSS */ @media (min-width: 768px) {    }  /* Using SCSS variables to store breakpoints */ $  breakpoint-tablet: 768px; @media (min-width: $  breakpoint-tablet) {    }

Another reason to write media queries with a preprocessor like Sass is that it can sometimes provide some precious help with the syntax, in particular when writing an expression with a logical or (represented with a comma in CSS).

For example, if you want to target retina devices, the pure CSS syntax starts getting a bit verbose:

/* Plain CSS */ @media (min-width: 768px) and         (-webkit-min-device-pixel-ratio: 2),         (min-width: 768px) and         (min-resolution: 192dpi) {  }  /* Using variables? */ @media (min-width: $  bp-tablet) and ($  retina) { // or #{$  retina}  } 

It does look nicer, but unfortunately it won’t work as expected.

A problem with logic

Because of the way the CSS “or” operator works, I wouldn’t be able to mix the retina conditions with other expressions since a (b or c) would be compiled into (a or b) c and not a b or a c.

$  retina: "(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)";   // This will generate unwanted results! @media (min-width: 480px) and #{$  retina} {   body {     background-color: red;   } }
/* Not the logic we're looking for */ @media (min-width: 480px) and (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {   body {     background-color: red;   } }

I realized I needed something more powerful, like a mixin or a function, to address this. I tried a few solutions.

Dmitry Sheiko’s technique

One I tried was Dmitry Sheiko’s technique, which had a nice syntax and includes Chris’ retina declaration.

// Predefined Break-points $  mediaMaxWidth: 1260px; $  mediaBp1Width: 960px; $  mediaMinWidth: 480px;  @function translate-media-condition($  c) {   $  condMap: (     "screen": "only screen",     "print": "only print",     "retina": "(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-device-pixel-ratio: 1.5), (min-resolution: 120dpi)",     ">maxWidth": "(min-width: #{$  mediaMaxWidth + 1})",     "<maxWidth": "(max-width: #{$  mediaMaxWidth})", 		     ">bp1Width": "(min-width: #{$  mediaBp1Width + 1})",     "<bp1Width": "(max-width: #{$  mediaBp1Width})",     ">minWidth": "(min-width: #{$  mediaMinWidth + 1})",     "<minWidth": "(max-width: #{$  mediaMinWidth})"   );   @return map-get( $  condMap, $  c ); }  // The mdia mixin @mixin media($  args...) {   $  query: "";   @each $  arg in $  args {     $  op: "";     @if ( $  query != "" ) {       $  op: " and ";     }     $  query: $  query + $  op + translate-media-condition($  arg);   }   @media #{$  query}  { @content; } }

But the problem with logical disjunction was still there.

.section {   @include media("retina", "<minWidth") {     color: white;   }; }
/* Not the logic we're looking for */ @media (-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3 / 2), (min-device-pixel-ratio: 1.5), (min-resolution: 120dpi) and (max-width: 480px) {   .section {     background: blue;     color: white;   } }

Landon Schropp’s technique

Landon Schropp’s was my next stop. Landon creates simple named mixins that do specific jobs. Like:

$  tablet-width: 768px; $  desktop-width: 1024px;  @mixin tablet {   @media (min-width: #{$  tablet-width}) and (max-width: #{$  desktop-width - 1px}) {     @content;   } }  @mixin desktop {   @media (min-width: #{$  desktop-width}) {     @content;   } }

He has a single-responsibility retina version as well.

But another problem hit me when I was styling an element that required additional rules on intermediate breakpoints. I didn’t want to pollute my list of global breakpoints with case-specific values just so I could still use the mixin, but I definitely didn’t want to forgo the mixin and go back to using plain CSS and hardcoding things every time I had to use custom values.

/* I didn't want to sometimes have this */ @include tablet {  }  /* And other times this */ @media (min-width: 768px) and (max-width: 950px) {  }

Breakpoint technique

Breakpoint-sass was next on my list, as it supports both variables and custom values in its syntax (and, as a bonus, it’s really clever with pixel ratio media queries).

I could write something like:

$  breakpoint-tablet: 768px;  @include breakpoint(453px $  breakpoint-tablet) {  }  @include breakpoint($  breakpoint-tablet 850px) {  }  /* Compiles to: */ @media (min-width: 453px) and (max-width: 768px) {  }  @media (min-width: 768px) and (max-width: 850px) {  }

Things were looking better, but I personally think that Breakpoint-sass’ syntax feels less natural than Dmitry’s. You can give it a number and it assumes it’s a min-width value, or a number and a string and it assumes a property/value pair, to name just a few of the combinations it supports.

That’s fine and I’m sure it works great once you’re used to it, but I hadn’t given up on finding a syntax that was both simple and as close as possible to the way I orally describe what a media query must target.

Also, if you look at the example above you’ll see that a device with a width of exactly 768px will trigger both media queries, which may not be exactly what we want. So I added the ability to write inclusive and exclusive breakpoints to my list of requirements.

My (Eduardo Bouças’s) technique

This is my take on it.

Clean syntax, dynamic declaration

I’m a fan of Dmitry’s syntax, so my solution was inspired by it. However, I’d like some more flexibility in the way breakpoints are created. Instead of hardcoding the names of the breakpoints in the mixin, I used a multidimensional map to declare and label them.

$  breakpoints: (phone: 640px,                 tablet: 768px,                 desktop: 1024px) !default;  @include media(">phone", "<tablet") { }  @include media(">tablet", "<950px") { }

The mixin comes with a set of default breakpoints, which you can override anywhere in the code by re-declaring the variable $ breakpoints.

Inclusive and exclusive breakpoints

I wanted to have a finer control over the intervals in the expressions, so I included support for the less-than-or-equal-to and greater-than-or-equal-to operators. This way I can use the same breakpoint declaration in two mutually exclusive media queries.

@include media(">=phone", "<tablet") {  }  @include media(">=tablet", "<=950px") {  }  /* Compiles to */ @media (min-width: 640px) and (max-width: 767px) {  }  @media (min-width: 768px) and (max-width: 950px) {  }

Infer media types and handle logic disjunction

Similarly to the breakpoints, there’s a list for media types and other static expressions declared by default (which you can override by setting the variable $ media-expressions). This adds support for optional media types, such as screen or handheld, but it’s also capable of correctly handling expressions with logical disjunctions, such as the retina media query we saw before. The disjunctions are declared as nested lists of strings.

$  media-expressions: (screen: "screen",                      handheld: "handheld",                     retina2x:                      ("(-webkit-min-device-pixel-ratio: 2)",                      "(min-resolution: 192dpi)")) !default;  @include media("screen", ">=tablet") {  }  @include media(">tablet", "<=desktop", "retina2x") {  }  /* Compiles to */ @media screen and (min-width: 768px) {  }  @media (min-width: 769px) and         (max-width: 1024px) and         (-webkit-min-device-pixel-ratio: 2),        (min-width: 769px) and         (max-width: 1024px) and         (min-resolution: 192dpi) {  }

There’s no rocket science under the hood, but the full implementation of the mixin isn’t something I could show in just a few lines of code. Instead of boring you with huge code snippets and neverending comments, I included a Pen with everything working and I’ll briefly describe the process it goes through to construct the media queries.

How it works

  1. The mixin receives multiple arguments as strings and starts by going through each one to figure out if it represents a breakpoint, a custom width, or one of the static media expressions.
  2. If an operator is found, it is extracted and any matching breakpoint will be returned, or else we assume it’s a custom value and cast it to a number (using SassyCast).
  3. If it’s a static media expression, it checks for any or operators and generates all the combinations necessary to represent the disjunction.
  4. The process is repeated for all the arguments and the results will by glued together by the and connector to form the media query expression.

If you’d like to look at the complete Sass for it, it’s here. It’s called include-media on GitHub.

Final thoughts

  • I’m a big fan of this technique to make Sass talk to JavaScript. Because we declare breakpoints as a multidimensional list with their names as keys, exporting them in bulk to JavaScript becomes really straightforward and can be done automatically with just a few lines of code.
  • I’m not trying to put down other people’s solutions and I’m definitely not saying this one is better. I mentioned them to show some of the obstacles I found along the way to my ideal solution, as well as some great things they introduced that inspired my own solution.
  • You might have some concerns about the length and complexity of this implementation. While I understand, the idea behind it is that you download one single file, @import it into your project and start using it without having to touch the source code. Ping me on Twitter though if you have any questions.
  • You can get it from GitHub and you are very welcome to contribute with issues/code/love. I’m sure there’s still a lot we can do to make it better.


Eduardo made a website for his approach: @include-media.

Direct Link to ArticlePermalink

The post Nested Media Queries appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, ,

Weekly Platform News: The :not() pseudo-class, Video Media Queries, clip-path: path() Support

Hey, we’re back with weekly updates about the browser landscape from Šime Vidas.

In this week’s update, the CSS :not pseudo class can accept complex selectors, how to disable smooth scrolling when using “Find on page…” in Chrome, Safari’s support for there media attribute on <video> elements, and the long-awaited debut of the path() function for the CSS clip-path property.

Let’s jump into the news…

The enhanced :not() pseudo-class enables new kinds of powerful selectors

After a years-long wait, the enhanced :not() pseudo-class has finally shipped in Chrome and Firefox, and is now supported in all major browser engines. This new version of :not() accepts complex selectors and even entire selector lists.

For example, you can now select all <p> elements that are not contained within an <article> element.

/* select all <p>s that are descendants of <article> */ article p { }  /* NEW! */ /* select all <p>s that are not descendants of <article> */ p:not(article *) { }

In another example, you may want to select the first list item that does not have the hidden attribute (or any other attribute, for that matter). The best selector for this task would be :nth-child(1 of :not([hidden])), but the of notation is still only supported in Safari. Luckily, this unsupported selector can now be re-written using only the enhanced :not() pseudo-class.

/* select all non-hidden elements that are not preceded by a non-hidden sibling (i.e., select the first non-hidden child */ :not([hidden]):not(:not([hidden]) ~ :not([hidden])) { }

The HTTP Refresh header can be an accessibility issue

The HTTP Refresh header (and equivalent HTML <meta> tag) is a very old and widely supported non-standard feature that instructs the browser to automatically and periodically reload the page after a given amount of time.

<!-- refresh page after 60 seconds --> <meta http-equiv="refresh" content="60">

According to Google’s data, the <meta http-equiv="refresh"> tag is used by a whopping 2.8% of page loads in Chrome (down from 4% a year ago). All these websites risk failing several success criteria of the Web Content Accessibility Guidelines (WCAG):

If the time interval is too short, and there is no way to turn auto-refresh off, people who are blind will not have enough time to make their screen readers read the page before the page refreshes unexpectedly and causes the screen reader to begin reading at the top.

However, WCAG does allow using the <meta http-equiv="refresh"> tag specifically with the value 0 to perform a client-side redirect in the case that the author does not control the server and hence cannot perform a proper HTTP redirect.

(via Stefan Judis)

How to disable smooth scrolling for the “Find on page…” feature in Chrome

CSS scroll-behavior: smooth is supported in Chrome and Firefox. When this declaration is set on the <html> element, the browser scrolls the page “in a smooth fashion.” This applies to navigations, the standard scrolling APIs (e.g., window.scrollTo({ top: 0 })), and scroll snapping operations (CSS Scroll Snap).

Unfortunately, Chrome erroneously keeps smooth scrolling enabled even when the user performs a text search on the page (“Find on page…” feature). Some people find this annoying. Until that is fixed, you can use Christian Schaefer’s clever CSS workaround that effectively disables smooth scrolling for the “Find on page…” feature only.

@keyframes smoothscroll1 {   from,   to {     scroll-behavior: smooth;   } }  @keyframes smoothscroll2 {   from,   to {     scroll-behavior: smooth;   } }  html {   animation: smoothscroll1 1s; }  html:focus-within {   animation-name: smoothscroll2;   scroll-behavior: smooth; }

In the following demo, notice how clicking the links scrolls the page smoothly while searching for the words “top” and “bottom” scrolls the page instantly.

Safari still supports the media attribute on video sources

With the HTML <video> element, it is possible to declare multiple video sources of different MIME types and encodings. This allows websites to use more modern and efficient video formats in supporting browsers, while providing a fallback for other browsers.

<video>   <source src="/flower.webm" type="video/webm">   <source src="/flower.mp4" type="video/mp4"> </video>

In the past, browsers also supported the media attribute on video sources. For example, a web page could load a higher-resolution video if the user’s viewport exceeded a certain size.

<video>   <source media="(min-width: 1200px)" src="/large.mp4" type="video/mp4">   <source src="/small.mp4" type="video/mp4"> </video>

The above syntax is in fact still supported in Safari today, but it was removed from other browsers around 2014 because it was not considered a good feature:

It is not appropriate for choosing between low resolution and high resolution because the environment can change (e.g., the user might fullscreen the video after it has begun loading and want high resolution). Also, bandwidth is not available in media queries, but even if it was, the user agent is in a better position to determine what is appropriate than the author.

Scott Jehl (Filament Group) argues that the removal of this feature was a mistake and that websites should be able to deliver responsive video sources using <video> alone.

For every video we embed in HTML, we’re stuck with the choice of serving source files that are potentially too large or small for many users’ devices … or resorting to more complicated server-side or scripted or third-party solutions to deliver a correct size.

Scott has written a proposal for the reintroduction of media in video <source> elements and is welcoming feedback.

The CSS clip-path: path() function ships in Chrome

It wasn’t mentioned in the latest “New in Chrome 88” article, but Chrome just shipped the path() function for the CSS clip-path property, which means that this feature is now supported in all three major browser engines (Safari, Firefox, and Chrome).

The path() function is defined in the CSS Shapes module, and it accepts an SVG path string. Chris calls this the ultimate syntax for the clip-path property because it can clip an element with “literally any shape.” For example, here’s a photo clipped with a heart shape:

The post Weekly Platform News: The :not() pseudo-class, Video Media Queries, clip-path: path() Support appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , , , , , , ,

Give Users Control: The Media Session API

Here’s a scenario. You start a banging Kendrick Lamar track in one of your many open browser tabs. You’re loving it, but someone walks into your space and you need to pause it. Which tab is it? Browsers try to help with that a little bit. You can probably mute the entire system audio. But wouldn’t it be nice to actually have control over the audio playback without necessarily needing to find your way back to that tab?

The Media Session API makes this possible. It gives media playback access to the user outside of the browser tab where it is playing. If implemented, it will be available in various places on the device, including:

  • the notifications area on many mobile devices,
  • on other wearables, and
  • the media hub area of many desktop devices.

In addition, the Media Session API allows us to control media playback with media keys and voice assistants like Siri, Google Assistant, Bixby, or Alexa.

The Media Session API

The Media Session API mainly consists of the two following interfaces:

  • MediaMetadata
  • MediaSession

The MediaMetadata interface is what provides data about the playing media. It is responsible for letting us know the media’s title, album, artwork and artist (which is Kendrick Lamar in this example). The MediaSession interface is what is responsible for the media playback functionality.

Before we take a deep dive into the topic, we would have to take note of feature detection. It is good practice to check if a browser supports a feature before implementing it. To check if a browser supports the Media Session API, we would have to include the following in our JavaScript file:

if ('mediaSession' in navigator) {   // Our media session api that lets us seek to the beginning of Kendrick Lamar's &quot;Alright&quot; }

The MediaMetadata interface

The constructor, MediaMetadata.MediaMetadata() creates a new MediaMetadata object. After creating it, we can add the following properties:

  • MediaMetadata.title sets or gets the title of the media playing.
  • MediaMetadata.artist sets or gets the name of the artist or group of the media playing.
  • MediaMetadata.album sets or gets the name of the album containing the media playing.
  • MediaMetadata.artwork sets or gets the array of images related with the media playing.

The value of the artwork property of the MediaMetadata object is an array of MediaImage objects. A MediaImage object contains details describing an image associated with the media. The objects have the three following properties:

  • src: the URL of the image
  • sizes: indicates the size of the image so one image does not have to be scaled
  • type: the MIME type of the image

Let’s create a MediaMetadata object for Kendrick Lamar’s “Alright” off his To Pimp a Butterfly album.

if ('mediaSession' in navigator) {   navigator.mediaSession.metadata = new MediaMetadata({     title: 'Alright',     artist: 'Kendrick Lamar',     album: 'To Pimp A Butterfly',     artwork: [       { src: 'https://mytechnicalarticle/kendrick-lamar/to-pimp-a-butterfly/alright/96x96', sizes: '96x96', type: 'image/png' },       { src: 'https://mytechnicalarticle/kendrick-lamar/to-pimp-a-butterfly/alright/128x128', sizes: '128x128', type: 'image/png' },       // More sizes, like 192x192, 256x256, 384x384, and 512x512     ]   }); }

The MediaSession interface

As stated earlier, this is what lets the user control the playback of the media. We can perform the following actions on the playing media through this interface:

  • play: play the media
  • pause: pause the media
  • previoustrack: switch to the previous track
  • nexttrack: switch to the next track
  • seekbackward: seek backward from the current position, by a few seconds
  • seekforward: seek forward from the current position, by a few seconds
  • seekto: seek to a specified time from the current position
  • stop: stop media playback
  • skipad: skip past the advertisement playing, if any

The MediaSessionAction enumerated type makes these actions available as string types. To support any of these actions, we have to use the MediaSession’s setActionHandler() method to define a handler for that action. The method takes the action, and a callback that is called when the user invokes the action. Let us take a not-too-deep dive to understand it better.

To set handlers for the play and pause actions, we include the following in our JavaScript file:

let alright = new HTMLAudioElement();  if ('mediaSession' in navigator) {   navigator.mediaSession.setActionHandler('play', () => {     alright.play();   });   navigator.mediaSession.setActionHandler('pause', () => {     alright.pause();   }); }

Here we set the track to play when the user plays it and pause when the user pauses it through the media interface.

For the previoustrack and nexttrack actions, we include the following:

let u = new HTMLAudioElement(); let forSaleInterlude = new HTMLAudioElement();  if ('mediaSession' in navigator) {   navigator.mediaSession.setActionHandler('previoustrack', () => {     u.play();   });   navigator.mediaSession.setActionHandler('nexttrack', () => {     forSaleInterlude.play();   }); }

This might not completely be self-explanatory if you are not much of a Kendrick Lamar fan but hopefully, you get the gist. When the user wants to play the previous track, we set the previous track to play. When it is the next track, it is the next track.

To implement the seekbackward and seekforward actions, we include the following:

if ('mediaSession' in navigator) {   navigator.mediaSession.setActionHandler('seekbackward', (details) => {     alright.currentTime = alright.currentTime - (details.seekOffset || 10);   });   navigator.mediaSession.setActionHandler('seekforward', (details) => {     alright.currentTime = alright.currentTime + (details.seekOffset || 10);   }); }

Given that I don’t consider any of this self-explanatory, I would like to give a concise explanation about the seekbackward and seekforward actions. The handlers for both actions, seekbackward and seekforward, are fired, as their names imply, when the user wants to seek backward or forward by a few number of seconds. The MediaSessionActionDetails dictionary provides us the “few number of seconds” in a property, seekOffset. However, the seekOffset property is not always present because not all user agents act the same way. When it is not present, we should set the track to seek backward or forward by a “few number of seconds” that makes sense to us. Hence, we use 10 seconds because it is quite a few. In a nutshell, we set the track to seek by seekOffset seconds if it is provided. If it is not provided, we seek by 10 seconds.

To add the seekto functionality to our Media Session API, we include the following snippet:

if ('mediaSession' in navigator) {   navigator.mediaSession.setActionHandler('seekto', (details) => {     if (details.fastSeek && 'fastSeek' in alright) {       alright.fastSeek(details.seekTime);       return;     }     alright.currentTime = details.seekTime;   }); }

Here, the MediaSessionActionDetails dictionary provides the fastSeek and seekTime properties. fastSeek is basically seek performed rapidly (like fast-forwarding or rewinding) while seekTime is the time the track should seek to. While fastSeek is an optional property, the MediaSessionActionDetails dictionary always provides the seekTime property for the seekto action handler. So fundamentally, we set the track to fastSeek to the seekTime when the property is available and the user fast seeks, while we just set it to the seekTime when the user just seeks to a specified time.

Although I wouldn’t know why one would want to stop a Kendrick song, it won’t hurt to describe the stop action handler of the MediaSession interface:

if ('mediaSession' in navigator) {   navigator.mediaSession.setActionHandler('stop', () => {     alright.pause();     alright.currentTime = 0;   }); } 

The user invokes the skipad (as in, “skip ad” rather than “ski pad”) action handler when an advertisement is playing and they want to skip it so they can continue listening to Kendrick Lamar’s “Alright track. If I’m being honest, the complete details of the skipad action handler is out of the scope of my “Media Session API” understanding. Hence, you should probably look that up on your own after reading this article, if you actually want to implement it.

Wrapping up

We should take note of something. Whenever the user plays the track, seeks, or changes the playback rate, we are supposed to update the position state on the interface provided by the Media Session API. What we use to implement this is the setPositionState() method of the mediaSession object, as in the following:

if ('mediaSession' in navigator) {   navigator.mediaSession.setPositionState({     duration: alright.duration,     playbackRate: alright.playbackRate,     position: alright.currentTime   }); }

In addition, I would like to remind you that not all browsers of the users would support all the actions. Therefore, it is recommended to set the action handlers in a try...catch block, as in the following:

const actionsAndHandlers = [   ['play', () => { /*...*/ }],   ['pause', () => { /*...*/ }],   ['previoustrack', () => { /*...*/ }],   ['nexttrack', () => { /*...*/ }],   ['seekbackward', (details) => { /*...*/ }],   ['seekforward', (details) => { /*...*/ }],   ['seekto', (details) => { /*...*/ }],   ['stop', () => { /*...*/ }] ]   for (const [action, handler] of actionsAndHandlers) {   try {     navigator.mediaSession.setActionHandler(action, handler);   } catch (error) {     console.log(`The media session action, $ {action}, is not supported`);   } }

Putting everything we have done, we would have the following:

let alright = new HTMLAudioElement(); let u = new HTMLAudioElement(); let forSaleInterlude = new HTMLAudioElement();  const updatePositionState = () => {   navigator.mediaSession.setPositionState({     duration: alright.duration,     playbackRate: alright.playbackRate,     position: alright.currentTime   }); }   const actionsAndHandlers = [   ['play', () => {     alright.play();     updatePositionState();   }],   ['pause', () => { alright.pause(); }],   ['previoustrack', () => { u.play(); }],   ['nexttrack', () => { forSaleInterlude.play(); }],   ['seekbackward', (details) => {     alright.currentTime = alright.currentTime - (details.seekOffset || 10);     updatePositionState();   }],   ['seekforward', (details) => {     alright.currentTime = alright.currentTime + (details.seekOffset || 10);     updatePositionState();   }],   ['seekto', (details) => {     if (details.fastSeek && 'fastSeek' in alright) {       alright.fastSeek(details.seekTime);       updatePositionState();       return;     }     alright.currentTime = details.seekTime;     updatePositionState();   }],   ['stop', () => {     alright.pause();     alright.currentTime = 0;   }], ]   if ( 'mediaSession' in navigator ) {   navigator.mediaSession.metadata = new MediaMetadata({     title: 'Alright',     artist: 'Kendrick Lamar',     album: 'To Pimp A Butterfly',     artwork: [       { src: 'https://mytechnicalarticle/kendrick-lamar/to-pimp-a-butterfly/alright/96x96', sizes: '96x96', type: 'image/png' },       { src: 'https://mytechnicalarticle/kendrick-lamar/to-pimp-a-butterfly/alright/128x128', sizes: '128x128', type: 'image/png' },       // More sizes, like 192x192, 256x256, 384x384, and 512x512     ]   });     for (const [action, handler] of actionsAndHandlers) {     try {       navigator.mediaSession.setActionHandler(action, handler);     } catch (error) {       console.log(`The media session action, $ {action}, is not supported`);     }   } }

Here’s a demo of the API:

I implemented six of the actions. Feel free to try the rest during your leisure.

If you view the Pen on your mobile device, notice how it appears on your notification area.

If your smart watch is paired to your device, take a sneak peek at it.

If you view the Pen on Chrome on desktop, navigate to the media hub and play with the media buttons there. The demo even has multiple tracks, so you experiment moving forward/back through tracks.

If you made it this far (or not), thanks for reading and please, on the next app you create with media functionality, implement this API.

The post Give Users Control: The Media Session API appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , ,

A Complete Guide to CSS Media Queries

Media queries are a way to target browser by certain characteristics, features, and user preferences, then apply styles or run other code based on those things. Perhaps the most common media queries in the world are those that target particular viewport ranges and apply custom styles, which birthed the whole idea of responsive design.

/* When the browser is at least 600px and above */ @media screen and (min-width: 600px) {   .element {     /* Apply some styles */   } }

There are lots of other things we can target beside viewport width. That might be screen resolution, device orientation, operating system preference, or even more among a whole bevy of things we can query and use to style content.

Looking for a quick list of media queries based on the viewports of standard devices, like phones, tablets and laptops? Check out our collection of snippets.

Using media queries

Media queries are commonly associated with CSS, but they can be used in HTML and JavaScript as well.


There are a few ways we can use media queries directly in HTML.

There’s the <link> element that goes right in the document <head>. In this example. we’re telling the browser that we want to use different stylesheets at different viewport sizes:

<html>   <head>     <!-- Served to all users -->     <link rel="stylesheet" href="all.css" media="all" />     <!-- Served to screens that are at least 20em wide -->     <link rel="stylesheet" href="small.css" media="(min-width: 20em)" />     <!-- Served to screens that are at least 64em wide -->     <link rel="stylesheet" href="medium.css" media="(min-width: 64em)" />     <!-- Served to screens that are at least 90em wide -->     <link rel="stylesheet" href="large.css" media="(min-width: 90em)" />     <!-- Served to screens that are at least 120em wide -->     <link rel="stylesheet" href="extra-large.css" media="(min-width: 120em)" />     <!-- Served to print media, like printers -->     <link rel="stylesheet" href="print.css" media="print" />   </head>   <!-- ... --> </html>

Why would you want to do that? It can be a nice way to fine-tune the performance of your site by splitting styles up in a way that they’re downloaded and served by the devices that need them.

But just to be clear, this doesn’t always prevent the stylesheets that don’t match those media queries from downloading, it just assigns them a low loading priority level. So, if a small screen device like a phone visits the site, it will only download the stylesheets in the media queries that match its viewport size. But if a larger desktop screen comes along, it will download the entire bunch because it matches all of those queries (well, minus the print query in this specific example).

That’s just the <link> element. As our guide to responsive images explains, we can use media queries on <source> element, which informs the <picture> element what version of an image the browser should use from a set of image options.

<picture>   <!-- Use this image if the screen is at least 800px wide -->   <source srcset="cat-landscape.png" media="(min-width: 800px)">   <!-- Use this image if the screen is at least 600px wide -->   <source srcset="cat-cropped.png" media="(min-width: 600px)">    <!-- Use this image if nothing matches -->   <img src="cat.png" alt="A calico cat with dark aviator sunglasses."> </picture>

Again, this can be a nice performance win because we can serve smaller images to smaller devices — which presumably (but not always) will be low powered devices that might be limited to a data plan.

And let’s not forget that we can use media queries directly on the <style> element as well:

<style>   p {     background-color: blue;     color: white;   } </style>  <style media="all and (max-width: 500px)">   p {     background-color: yellow;     color: blue;   } </style>

Again, CSS is the most common place to spot a media query in the wild. They go right in the stylesheet in an @media rule that wraps elements with conditions for when and where to apply a set of styles when a browser matches those conditions.

/* Viewports between 320px and 480px wide */ @media only screen and (min-device-width: 320px) and (max-device-width: 480px)   .card {     background: #bada55;   } }

It’s also possible to scope imported style sheet but as a general rule avoid using @import since it performs poorly.

/* Avoid using @import if possible! */  /* Base styles for all screens */ @import url("style.css") screen; /* Styles for screens in a portrait (narrow) orientation */ @import url('landscape.css') screen and (orientation: portrait); /* Print styles */ @import url("print.css") print;

We can use media queries in JavaScript, too! And guess, what? They’re work a lot like they do in CSS. The difference? We start by using the window.matchMedia() method to define the conditions first.

So, say we want to log a message to the console when the browser is at least 768px wide. We can create a constant that calls matchMedia() and defines that screen width:

// Create a media condition that targets viewports at least 768px wide const mediaQuery = window.matchMedia( '( min-width: 768px )' )

Then we can fire log to the console when that condition is matched:

// Create a media condition that targets viewports at least 768px wide const mediaQuery = window.matchMedia( '( min-width: 768px )' ) 
 // Check if the media query is true if ( mediaQuery ) {   // Then log the following message to the console   console.log('Media Query Matched!') }

Unfortunately, this only fires once so if the alert is dismissed, it won’t fire again if we change the screen width and try again without refreshing. That’s why it’s a good idea to use a listener that checks for updates.

// Create a condition that targets viewports at least 768px wide const mediaQuery = window.matchMedia('(min-width: 768px)') 
 function handleTabletChange(e) {   // Check if the media query is true   if (e) {     // Then log the following message to the console     console.log('Media Query Matched!')   } } 
 // Register event listener mediaQuery.addListener(handleTabletChange)  // Initial check handleTabletChange(mediaQuery)

Check out Marko Ilic’s full post on “Working with JavaScript Media Queries” for a deeper dive on this, including a comparison of using media queries with an older JavaScript approach that binds a resize event listener that checks window.innerWidth or window.innerHeight to fire changes.

Anatomy of a Media Query

Now that we’ve seen several examples of where media queries can be used, let’s pick them apart and see what they’re actually doing.

@media [media-type] ([media-feature]) {   /* Styles! */ }

The first ingredient in a media query recipe is the @media rule itself, which is one of many CSS at-rules. Why does @media get all the attention? Because it’s geared to the type of media that a site is viewed with, what features that media type supports, and operators that can be combined to mix and match simple and complex conditions alike.

Media types
@media screen {   /* Styles! */ }

What type of media are we trying to target? In many (if not most) cases, you’ll see a screen value used here, which makes sense since many of the media types we’re trying to match are devices with screens attached to them.

But screens aren’t the only type of media we can target, of course. We have a few, including:

  • all: Matches all devices
  • print: Matches documents that are viewed in a print preview or any media that breaks the content up into pages intended to print.
  • screen: Matches devices with a screen
  • speech: Matches devices that read the content audibly, such as a screenreader. This replaces the now deprecated aural type since Media Queries Level 4.

To preview print styles in a screen all major browsers can emulate the output of a print stylesheet using DevTools. Other media types such as tty, tv,  projection,  handheld, braille, embossed and aural have been deprecated and, while the spec continues to advise browsers to recognize them, they must evaluate to nothing. If you are using one of these consider changing it for a modern approach.

Media features

Once we define the type of media we’re trying to match, we can start defining what features we are trying to match it to. We’ve looked at a lot of examples that match screens to width, where screen is the type and both min-width and max-width are features with specific values.

But there are many, many (many!) more “features” we can match. Media Queries Level 4 groups 18 media features into 5 categories.

Viewport/Page Characteristics

Feature Summary Values Added
width Defines the widths of the viewport. This can be a specific number (e.g. 400px) or a range (using min-width and max-width). <length>
height Defines the height of the viewport. This can be a specific number (e.g. 400px) or a range (using min-height and max-height). <length>
aspect-ratio Defines the width-to-height aspect ratio of the viewport <ratio>
orientation The way the screen is oriented, such as tall (portrait) or wide (landscape) based on how the device is rotated. portrait


overflow-block Checks how the device treats content that overflows the viewport in the block direction, which can be scroll (allows scrolling), optional-paged (allows scrolling and manual page breaks), paged (broken up into pages), and none (not displayed). scroll



Media Queries Level 4
overflow-inline Checks if content that overflows the viewport along the inline axis be scrolled, which is either none (no scrolling) or scroll (allows scrolling). scroll


Media Queries Level 4

Display Quality

Feature Summary Values Added
resolution Defines the target pixel density of the device <resolution>


scan Defines the scanning process of the device, which is the way the device paints an image onto the screen (where interlace draws odd and even lines alternately, and progressive draws them all in sequence). interlace


grid Determines if the device uses a grid (1) or bitmap (0) screen 0 = Bitmap
1 = Grid
Media Queries Level 5
update Checks how frequently the device can modify the appearance of content (if it can at all), with values including none, slow and fast. slow



Media Queries Level 4
environment-blending A method for determining the external environment of a device, such as dim or excessively bright places. opaque



display-mode Tests the display mode of a device, including fullscreen(no browsers chrome), standalone (a standalone application), minimal-ui (a standalone application, but with some navigation), and browser (a more traditional browser window) fullscreen




Web App Manifest


Feature Summary Values Added
color Defines the color support of a device, expressed numerically as bits. So, a value of 12 would be the equivalent of a device that supports 12-bit color, and a value of zero indicates no color support. <integer>
color-index Defines the number of values the device supports. This can be a specific number (e.g. 10000) or a range (e.g. min-color-index: 10000, max-color-index: 15000), just like width. <integer>
monochrome The number of bits per pixel that a device’s monochrome supports, where zero is no monochrome support. <integer>
color-gamut Defines the range of colors supported by the browser and device, which could be srgb, p3 or rec2020 srgb



Media Queries Level 4
dynamic-range The combination of how much brightness, color depth, and contrast ratio supported by the video plane of the browser and user device. standard


inverted-colors Checks if the browser or operating system is set to invert colors (which can be useful for optimizing accessibility for sight impairments involving color) inverted


Media Queries Level 5


Feature Summary Values Added
pointer Sort of like any-pointer but checks if the primary input mechanism is a pointer and, if so, how accurate it is (where coarse is less accurate, fine is more accurate, and none is no pointer). coarse



Media Queries Level 4
hover Sort of like any-hover but checks if the primary input mechanism (e.g. mouse of touch) allows the user to hover over elements hover


Media Queries Level 4
any-pointer Checks if the device uses a pointer, such as a mouse or styles, as well as how accurate it is (where coarse is less accurate and fine is more accurate) coarse



Media Queries Level 4
any-hover Checks if the device is capable of hovering elements, like with a mouse or stylus. In some rare cases, touch devices are capable of hovers. hover


Media Queries Level 4

Video Prefixed

The spec references user agents, including TVs, that render video and graphics in two separate planes that each have their own characteristics. The following features describe those planes.

Feature Summary Values Added
video-color-gamut Describes the approximate range of colors supported by the video plane of the browser and user device srgb



Media Queries Level 5
video-dynamic-range The combination of how much brightness, color depth, and contrast ratio supported by the video plane of the browser and user device. standard


Media Queries Level 5
video-width¹ The width of the video plane area of the targeted display <length> Media Queries Level 5
video-height¹ The height of the video plane area of the targeted display <length> Media Queries Level 5
video-resolution¹ The resolution of the video plane area of the targeted display <resolution>


Media Queries Level 5
¹ Under discussion (Issue #5044)


Feature Summary Values Added
scripting Checks whether the device allows scripting (i.e. JavaScript) where enabled allows scripting, iniital-only enabled


Media Queries Level 5

User Preference

Feature Summary Values Added
prefers-reduced-motion Detects if the user’s system settings are set to reduce motion on the page, which is a great accessibility check. no-preference


Media Queries Level 5
prefers-reduced-transparency Detects if the user’s system settings prevent transparent across elements. no-preference


Media Queries Level 5
prefers-contrast Detects if the user’s system settings are set to either increase or decrease the amount of contrast between colors. no-preference




Media Queries Level 5
prefers-color-scheme Detects if the user prefers a light or dark color scheme, which is a rapidly growing way to go about creating “dark mode” interfaces. light


Media Queries Level 5
forced-colors Tests whether the browser restricts the colors available to use (which is none or active) active


Media Queries Level 5
prefers-reduced-data Detects if the user prefers to use less data for the page to be rendered. no-preference


Media Queries Level 5


Name Summary Removed
device-aspect-ratio The width-to-height aspect ratio of the output device Media Queries Level 4
device-height The height of the device’s surface that displays rendered elements Media Queries Level 4
device-width The width of the device’s surface that displays rendered elements Media Queries Level 4

Media queries support logical operators like many programming languages so that we can match media types based on certain conditions. The @media rule is itself a logical operator that is basically stating that “if” the following types and features are matches, then do some stuff.


But we can use the and operator if we want to target screens within a range of widths:

/* Matches screen between 320px AND 768px */ @media screen (min-width: 320px) and (max-width: 768px) {   .element {     /* Styles! */   } }

or (or comma-separated)

We can also comma-separate features as a way of using an or operator to match different ones:

/*    Matches screens where either the user prefers dark mode or the screen is at least 1200px wide */ @media screen (prefers-color-scheme: dark), (min-width 1200px) {   .element {     /* Styles! */   } }


Perhaps we want to target devices by what they do not support or match. This declaration removes the body’s background color when the device is a printer and can only show one color.

@media print and ( not(color) ) {   body {     background-color: none;   } }

Do you really need a media queries?

Media Queries is a powerful tool in your CSS toolbox with exciting hidden gems. But if you accomodate your design to every possible situation you’ll end up with a codebase that’s too complex to maintain and, as we all know, CSS is like a bear cub: cute and inoffensive but when it grows it will eat you alive.

That’s why I recommend following Ranald Mace’s concept of Universal Design which is “the design of products to be usable by all people, to the greatest extent possible, without the need for adaptation or specialized design.” 

On Accessibility for Everyone Laura Kalbag explains that the difference between accessible and universal design is subtle but important. An accessible designer would create a large door for people on a wheel chair to enter, while a universal designer would produce an entry that anyone would fit disregarding of their abilities.

I know that talking about universal design on the web is hard and almost sound utopian, but think about it, there are around 150 different browsers, around 50 different combinations of user preferences, and as we mentioned before more than 24000 different and unique Android devices alone. This means that there are at least 18 million possible cases in which your content might be displayed. In the words of the fantastic Miriam Suzanne “CSS out here trying to do graphic design of unknown content on an infinite and unknown canvas, across operating systems, interfaces, & languages. There’s no possible way for any of us to know what we’re doing.”

That’s why assuming is really dangerous, so when you design, develop and think about your products leave assumptions behind and use media queries to make sure that your content is displayed correctly in any contact and before any user.

Using min- and max- to match value ranges

Many of the media features outlined in the previous section — including widthheight, color and color-index — can be prefixed with min- or max- to express minimum or maximum constraints. We’ve already seen these in use throughout many of the examples, but the point is that we can create a range of value to match instead of having to declare specific values.

In the following snippet, we’re painting the body’s background purple when the viewport width is wider than 30em and narrower than 80em. If the viewport width does not match that range of values, then it will fallback to white.

body {   background-color: #fff; }  @media (min-width: 30em) and (max-width: 80em) {   body {     background-color: purple;   } }

Media Queries Level 4 specifies a new and simpler syntax using less then (>), greater than (<) and equals (=) operators. Unfortunately, at the time of writing, it isn’t supported by any major browser.

Nesting and complex decision making

CSS allows you to nest at-rules or group statements using parentheses, making it possible to go as deep as we want to evaluate complex operations.

@media (min-width: 20em), not all and (min-height: 40em) {     @media not all and (pointer: none) { ... }   @media screen and ( (min-width: 50em) and (orientation: landscape) ), print and ( not (color) ) { ... } }

Be careful! even thought it’s possible to create powerful and complex expressions, you might end up with a very opinionated, hard to maintain query. As Brad Frost puts it: “The more complex our interfaces are, the more we have to think to maintain them properly.”


Many of the features added in Media Queries Level 4 are centered around accessibility.


prefers-reduced-motion detects if the user has the reduced motion preference activated to minimize the amount of movements and animations. It takes two values:

  • no-preference: Indicates that the user has made no preference known to the system.
  • reduce: Indicates that user has notified the system that they prefer an interface that minimizes the amount of movement or animation, preferably to the point where all non-essential movement is removed.

This preference is generally used by people who suffer from vestibular disorder or vertigo, where different movements result in loss of balance, migraine, nausea or hearing loss. If you ever tried to spin quickly and got dizzy, you know what it feels like.

In a fantastic article by Eric Bailey, he suggests stopping all animations with this code:

@media screen and (prefers-reduced-motion: reduce) {     * {     /* Very short durations means JavaScript that relies on events still works */     animation-duration: 0.001ms !important;     animation-iteration-count: 1 !important;     transition-duration: 0.001ms !important;   } }

Popular frameworks like Bootstrap have this feature on by default. In my opinion there is no excuse not to use prefers-reduced-motion — just use it. 


The prefers-contrast feature informs whether the user has chosen to increase or reduce contrast in their system preferences or the browser settings. It takes three values:

  • no-preference: When a user has made no preference known to the system. If you use it as a boolean it’ll evaluate false.
  • high: When a user has selected the option to display a higher level of contrast.
  • low: When a user has selected the option to display a lower level of contrast.

At the moment of writing this feature is not supported by any browser. Microsoft has done a non-standard earlier implementation with the -ms-high-contrast feature that works only on Microsoft Edge v18 or earlier (but not Chromium-based versions).

.button {   background-color: #0958d8;   color: #fff; }  @media (prefers-contrast: high) {   .button {     background-color: #0a0db7;   } }

This example is increasing the contrast of a the class button from AA to AAA when the user has high contrast on.


The inverted-colors feature informs whether the user has chosen to invert the colors on their system preferences or the browser settings. Sometimes this option is used as an alternative to high contrast. It takes three values:

  • none: When colors are displayed normally
  • inverted: When a user has selected the option to invert colors

The problem with invested colors is that it’ll also invert the colors of images and videos, making them look like x-ray images. By using a CSS invert filter you can select all images and videos and invert them back.

@media (inverted-colors) {   img, video {      filter: invert(100%);   } }

At the time of writing this feature is only supported by Safari.


Having a “dark mode” color scheme is something we’re seeing a lot more of these days, and thanks to the prefers-color-scheme feature, we can tap into a user’s system or browser preferences to determine whether we serve a “dark” or a “light” theme based on the ir preferences.

It takes two values:

  • light: When a user has selected that they prefer a light theme or has no active preferences
  • dark: When a user has selected a dark display in their settings
body {   --bg-color: white;    --text-color: black;    background-color: var(--bg-color);   color: var(--text-color); }  @media screen and (prefers-color-scheme: light) {   body {     --bg-color: black;     --text-color:white;   } }

As Adhuham explains in the complete guide to Dark Mode there is way more to it than just changing the color of the background. Before you jump into doing dark mode remember that if you don’t have a very smart implementation strategy you might end up with a code base that’s really hard to maintain. CSS variables can do wonders for it but that’s a subject for another article.

What lies ahead?

Media Queries Level 5 is currently in Working Draft status, which means a lot can change between now and when it becomes a recommendation. But it includes interesting features that are worth mentioning because they open up new ways to target screens and adapt designs to very specific conditions.

User preference media features

Hey, we just covered these in the last section! Oh well. These features are exciting because they’re informed by a user’s actual settings, whether they are from the user agent or even at the operating system level.

Detecting a forced color palette

This is neat. Some browsers will limit the number of available colors that can be used to render styles. This is called “forced colors mode” and, if enabled in the browser settings, the user can choose a limited set of colors to use on a page. As a result, the user is able to define color combinations and contrasts that make content more comfortable to read.

The forced-colors feature allows us to detect if a forced color palette is in use with the active value. If matched, the browser must provide the required color palette through the CSS system colors. The browser is also given the leeway to determine if the background color of the page is light or dark and, if appropriate, trigger the appropriate prefers-color-scheme value so we can adjust the page.

Detecting the maximum brightness, color depth, and contrast ratio

Some devices (and browsers) are capable of super bright displays, rendering a wide range of colors, and high contrast ratios between colors. We can detect those devices using the dynamic-range feature, where the high keyword matches these devices and standard matches everything else.

We’re likely to see changes to this because, as of right now, there’s still uncertainty about what measurements constitute “high” levels of brightness and contrast. The browser may get to make that determination.

Video prefixed features

The spec talks about some screens, like TVs, that are capable of displaying video and graphics on separate “planes” which might be a way of distinguishing the video frame from other elements on the screen. As such, Media Queries Level 5 is proposing a new set of media features aimed at detecting video characteristics, including color gamut and dynamic range.

There are also proposals to detect video height, width and resolution, but the jury’s still out on whether those are the right ways to address video.

Browser support

Browsers keep evolving and since by the time you are reading this post chances are that browser support for this feature might change, please check MDN updated browser compatibility table.

A note on container queries

Wouldn’t be cool if components could adapt themselves on their own size instead of the browser’s? That’s what the concept of container queries is all about. We currently only have the browser screen to make those changes via media queries. That’s unfortunate, as the viewport isn’t always a direct relationship to how big the element itself is. Imagine a widget that renders in many different contexts on a site: sometimes in a sidebar, sometimes in a full-width footer, sometimes in a grid with unknown columns.

This is the problem that the elusive container queries idea is trying to solve. Ideally we could adapt styles of an element according to the size of itself instead of of the size of the viewport. But container queries don’t exist yet. The WICG is looking for use cases and it’s a highly requested feature. We see occasional movement, but it’s unsure if we’ll ever get it. But when we do, you can bet that it will have an impact on how we approach media queries as well.

In the meantime, you can catch up on the origin story of container queries for more context.


Let’s look at a bunch of media query examples. There are so many combinations of media types, features, and operators that the number of possibilities we could show would be exhaustive. Instead, we’ll highlight a handful based on specific media features.

Adjust layout at different viewport widths

More info

This is the probably the most widely used media feature. It informs the width of the browser’s viewport including the scrollbar. It unlocked the CSS implementation of what Ethan Marcotte famously coined responsive design: a process by which a design responds to the size of the viewport using a combination of a fluid grid, flexible images, and responsive typesetting.

Later, Luke Wroblewski evolved the concept of responsive design by introducing the term mobile-first, encouraging designers and developers to start with the small-screen experience first then progressively enhance the experience as the screen width and device capabilities expand. A mobile-first can usually be spotted by it’s use of min-width instead of max-width. If we start with min-width, we’re essentially saying, “hey, browser, start here and work up.” On the flip side, max-width is sort of like prioritizing larger screens.

One approach for defining breakpoints by width is using the dimensions of standard devices, like the exact pixel width of an iPhone. But there are many, many (many), many different phones, tables, laptops, and desktops. Looking at Android alone, there are more than 24,000 variations of viewport sizes, resolutions, operating systems, and browsers, as of August 2015. So, while targeting the precise width of a specific device might be helpful for troubleshooting or one-off fixes, it’s probably not the most robust solution for maintaining a responsive architecture. This isn’t a new idea by any stretch. Brad Frost was already preaching the virtues of letting content — not devices — determine breakpoints in his post “7 habits of highly effective media queries” published back in 2013.

And even though media queries are still a valid tool to create responsive interfaces, there are many situations where it’s possible to avoid using width at all. Modern CSS allow us to create flexible layouts with CSS grid and flex that adapts our content to the viewport size without a need to add breakpoints. For example, here is a grid layout that adapts how many columns it will have without any media queries at all.

.container {   display: grid;   grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }

There are many articles about thinking beyond width, I wrote about it a few years ago and I recommend checking out Una Kravet’s Ten modern layouts in one line of CSS.

Dark mode

More info

This example is pulled straight from our Guide to Dark Mode on the Web. The idea is that we can detect whether a user’s system settings are configured to light or dark mode using the prefers-color-scheme feature and then define an alternate set of colors for the rendered UI.

Combining this technique with CSS custom properties makes things even easier because they act like variables that we only need to define once, then use throughout the code. Need to swap colors? Change the custom property value and it updates everywhere. That’s exactly what prefers-color-scheme does. We define a set of colors as custom properties, then redefine them inside a media query using the prefer-color-scheme feature to change colors based on the user’s settings.

Detecting orientation, hover and motion on a responsive card gallery

More info

This gallery is responsive without using the width feature.

It detects the orientation of the viewport. If it’s a portrait viewport, the sidebar will became a header; if it’s landscape it stays off to the side.

Using the pointer media feature, it decides if the main input device is coarse — like a finger — or fine — like a mouse cursor — to set the size of the clickable areas of the checkboxes.

Then, by using the hover media feature, the example checks if the device is capable of hovering (like a mouse cursor) and display a checkbox in each card.

The animations are removed when prefers-reduced-motion is set to reduce.

And did you notice something? We’re actually not using media queries for the actual layout and sizing of the cards! That’s handled using the minmax() function on the .container element to show how responsive design doesn’t always mean using media queries.

In short, this is a fully responsive app without ever measuring width or making assumptions.

Target an iPhone in landscape mode

/* iPhone X Landscape */ @media only screen    and (min-device-width: 375px)    and (max-device-width: 812px)    and (-webkit-min-device-pixel-ratio: 3)   and (orientation: landscape) {    /* Styles! */ }
More info

The orientation media feature tests whether a device is rotated the wide way (landscape) or the tall way (portrait).

While media queries are unable to know exactly which device is being used, we can use the exact dimensions of a specific device. The snippet above is targets the iPhone X.

Apply a sticky header for large viewports

More info

In the example above, we’re using height to detached fixed elements and avoid taking up too much screen real estate when the screen is too short. A horizontal navigation bar is in a fixed position when the screen is tall, but detaches itself on shorter screens.

Like the width feature, height detects the height of the viewport, including the scrollbar. Many of us browse the web on small devices with narrow viewports, making designing for different heights more relevant than ever. Anthony Colangelo describes how Apple uses the height media feature in a meaningful way to deal with the size of the hero image as the viewport’s height changes.

Responsive (fluid) typography

More info

A font can look either too big or too small, depending on the size of the screen that’s showing it. If we’re working on a small screen, then chances are that we’ll want to use smaller type than what we’d use on a much larger screen.

The idea here is that we’re using the browser’s width to scale the font size. We set a default font size on the <html> that acts as the “small” font size, then set another font size using a media query that acts as the “large” font size. In the middle? We set the font size again, but inside another media query that calculates a size based on the browser width.

The beauty of this is that it allows the font size to adjust based on the browser width, but never go above or below certain sizes. However, there is a much simpler way to go about this that requires no media queries at all, thanks to newer CSS features, like min(), max(), and clamp().

Provide bigger touch targets when devices have a course pointer

More info

Have you ever visited a site that had super tiny buttons? Some of us have fat fingers making it tough to tap an object accurately without inadvertently tapping something else instead.

Sure, we can rely on the width feature to tell if we’re dealing with a small screen, but we can also detect if the device is capable of hovering over elements. If it isn’t then it’s probably a touch device, or perhaps a device that supports both, like the Microsoft Surface.

The demo above uses checkboxes as an example. Checkboxes can be a pain to tap on when viewing them on a small screen, so we’re increasing the size and not requiring a hover if the device is incapable of hover events.

Again, this approach isn’t always accurate. Check out Patrick Lauke’s thorough article that details potential issues working with hover, pointer, any-hover and any-pointer.


Special thanks to Sarah Rambacher who helped to review this guide.

The post A Complete Guide to CSS Media Queries appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , ,

Interaction Media Features and Their Potential (for Incorrect Assumptions)

This is an updated and greatly expanded version of the article originally published on dev.opera back in 2015. That article referenced the Editor’s Draft, 24 March 2015 of the specification Media Queries Level 4, and contained a fairly big misunderstanding about how any-hover:none would end up being evaluated by browsers in practice.

The spec has since been updated (including clarifications and examples that I submitted following the publication of the original article), so this updated version removes the incorrect information of the original and brings the explanations in line with the most recent working draft. It also covers additional aspects relating to JavaScript touch/input detection.

The Media Queries Level 4 Interaction Media Featurespointer, hover, any-pointer and any-hover — are meant to allow sites to implement different styles and functionality (either CSS-specific interactivity like :hover, or JavaScript behaviors, when queried using window.matchMedia), depending on the particular characteristics of a user’s input devices.

Although the specification is still in working draft, interaction media features are generally well supported, though, to date, there are still some issues and inconsistencies in the various browser implementations — see the recent pointer/hover/any-pointer/any-hover test results, with references to relevant browser bugs.

Common use cases cited for interaction media features are often “make controls bigger/smaller depending on whether the users has a touchscreen device or is using a mouse/stylus” and “only use a CSS dropdown menu if the user has an input that allows hover-based interactions.”

@media (pointer: fine) {   /* using a mouse or stylus - ok to use small buttons/controls */ } @media (pointer: coarse) {   /* using touch - make buttons and other "touch targets" bigger */ } @media (hover: hover) {   /* ok to use :hover-based menus */ } @media (hover: none) {   /* don't use :hover-based menus */ }

There are also examples of developers using these new interaction media features as a way of achieving standards-based “touch detection,” often just for listening to touch events when the device is identified as having a coarse pointer.

if (window.matchMedia && window.matchMedia("(pointer:coarse)").matches) {   /* if the pointer is coarse, listen to touch events */   target.addEventListener("touchstart", ...);   // ... } else {   /* otherwise, listen to mouse and keyboard events */   // ... }

However, these approaches are slightly naive, and stem from a misunderstanding of what these interaction media queries are designed to tell us.

What’s the primary input?

One of the limitations of pointer and hover is that, by design, they only expose the characteristics of what a browser deems to be the primary pointer input. What the browser thinks, and what a user is actually using as their primary input, may differ — particularly now that the lines between devices, and the types of inputs they support, is becoming more and more blurry.

Microsoft Surface with a keyboard, trackpad, external bluetooth mouse, touchscreen.
Which one’s the “primary” input? the answer may depend on the activity.

Right out of the gate, it’s worth noting that interaction media features only cover pointer inputs (mouse, stylus, touchscreen). They don’t provide any way of detecting if a user’s primary input is a keyboard or keyboard-like interface, such as a switch control. In theory, for a keyboard user, a browser could report pointer: none, signaling that the user’s primary input is not a pointer at all. However, in practice, no browser offers a way for users to specify that they are in fact keyboard users. So keep in mind that, regardless of what the interaction media feature queries may return, it’s worth making sure that your site or app also works for keyboard users.

Traditionally, we could say that a phone or tablet’s primary input is the touchscreen. However, even on these devices, a user may have an additional input, like a paired bluetooth mouse (a feature that has been available for years on Android, is now supported in iPadOS, and is sure land in iOS), that they are using as their primary input.

An Android phone with a paired bluetooth keyboard and mouse, with the screen showing an actual mouse pointer and right-click context menu in Chrome

An iPad with a paired bluetooth keyboard, mouse, and Apple Pencil, with the screen showing the mouse “dot” and right-click context menu in Safari

In this case, while the device nominally has pointer: coarse and hover: none, users may actually be using a fine pointer device that is capable of hovers. Similarly, if a user has a stylus (like the Apple Pencil), their primary input may still be reported as the touchscreen, but rather than pointer: coarse, they now have an input that can provide fine pointer accuracy.

In these particular scenarios, if all the site is doing is making buttons and controls bigger and avoiding hover-based interactions, that would not be a major problem for the user: despite using a fine and hover-capable mouse, or a fine but still not hover-capable stylus, they will get styling and functionality aimed at the coarse, non-hover-capable touchscreen.

If the site is using the cues from pointer: coarse for more drastic changes, such as then only listening to touch events, then that will be problematic for users — see the section about incorrect assumptions that can completely break the experience.

However, consider the opposite: a “regular” desktop or laptop with a touchscreen, like Microsoft’s Surface. In most cases, the primary input will be the trackpad/mouse — with pointer:fine and hover:hover — but the user may well be using the touchscreen, which has coarse pointer accuracy and does not have hover capability. If styling and functionality are then tailored specifically to rely on the characteristics of the trackpad/mouse, the user may find it problematic or impossible to use the coarse, non-hover-capable touchscreen.

Feature Touchscreen Touchscreen + Mouse Desktop/Laptop Desktop/Laptop + Touchscreen
pointer:coarse true true false false
pointer:fine false false true true
hover:none true true false false
hover:hover false false true true

For a similar take on this problem, see ”The Good & Bad of Level 4 Media Queries” by Stu Cox. While it refers to an even earlier iteration of the spec that only contained pointer and hover and a requirement for these features to report the least capable, rather than the primary, input device.

The problem with the original pointer and hover on their own is that they don’t account for multi-input scenarios, and they rely on the browser to be able to correctly pick a single primary input. That’s where any-pointer and any-hover come into play.

Testing the capabilities of all inputs

Instead of focusing purely on the primary pointer input, any-pointer and any-hover report the combined capabilities of all available pointer inputs.

In order to support multi-input scenarios, where different (pointer-based) inputs may have different characteristics, more than one of the values for any-pointer (and, theoretically, any-hover, but this aspect is useless as we’ll see later) can match, if different input devices have different characteristics< (compared to pointer and hover, which only ever refer to the capabilities of the primary pointer input). In current implementations, these media features generally evaluate as follows:

Feature Touchscreen Touchscreen + Mouse Desktop/Laptop Desktop/Laptop + Touchscreen
any-pointer:coarse true true false true
any-pointer:fine false true true true
any-hover:none false false false false
any-hover:hover false true true true
Comparison of Firefox on Android’s media query results with just the touchscreen, and when adding a bluetooth mouse. Note how pointer and hover remain the same, but any-pointer and any-hover change to cover the new hover-capable fine input.

Going back to the original use cases for the interaction media features, instead of basing our decision to provide larger or smaller inputs or to enable hover-based functionality only on the characteristics of the primary pointer input, we can make that decision based on the characteristics of any available pointer inputs. Roughly translated, instead of saying “make all controls bigger if the primary input has pointer: coarse” or “only offer a CSS menu if the primary input has hover: hover,” we can build media queries that equate to saying, “if any of the pointer inputs is coarse, make the controls bigger” and “only offer a hover-based menu if at least one of the pointer inputs available to the user is hover-capable.”

@media (any-pointer: coarse) {   /* at least one of the pointer inputs     is coarse, best to make buttons and      other "touch targets" bigger (using      the query "defensively" to target      the least capable input) */ } @media (any-hover: hover) {   /* at least one of the inputs is       hover-capable, so it's at least       possible for users to trigger      hover-based menus */ }

Due to the way that any-pointer and any-hover are currently defined (as “the union of capabilities of all pointing devices available to the user”), any-pointer: none will only ever evaluate to true if there are no pointer inputs available, and, more crucially, any-hover: none will only ever be true if none of the pointer inputs present are hover-capable. Particularly for the latter, it’s therefore not possible to use the any-hover: none query to determine if only one or more of the pointer inputs present is not hover-capable — we can only use this media feature query to determine whether or not all inputs are not hover-capable, which is something that can just as well be achieved by checking if any-hover: hover evaluates to false. This makes the any-hover: none query essentially redundant.

We could work around this by inferring that if any-pointer: coarse is true, it’s likely a touchscreen, and generally those inputs are not hover-capable, but conceptually, we’re making assumptions here, and the moment there’s a coarse pointer that is also hover-capable, that logic falls apart. (And for those doubting that we may ever see a touchscreen with hover, remember that some devices, like the Samsung Galaxy Note and Microsoft’s Surface, have a hover-capable stylus that is detected even when it’s not touching the digitizer/screen, so some form of “hovering touch” detection may not be out of the question in the future.)

Combining queries for more educated guesses

The information provided by any-pointer and any-hover can of course be combined with pointer and hover, as well as the browser’s determination of what the primary input is capable of, for some slightly more nuanced assessments.

@media (pointer: coarse) and (any-pointer: fine) {   /* the primary input is a touchscreen, but      there is also a fine input (a mouse or       perhaps stylus) present. Make the design      touch-first, mouse/stylus users can      still use this just fine (though it may       feel a big clunky for them?) */ } @media (pointer: fine) and (any-pointer: coarse) {   /* the primary input is a mouse/stylus,      but there is also a touchscreen       present. May be safest to make       controls big, just in case users do       actually use the touchscreen? */ } @media (hover: none) and (any-hover: hover) {   /* the primary input can't hover, but      the user has at least one other      input available that would let them      hover. Do you trust that the primary      input is in fact what the user is       more likely to use, and omit hover-      based interactions? Or treat hover       as as something optional — can be       used (e.g. to provide shortcuts) to       users that do use the mouse, but       don't rely on it? */ }

Dynamic changes

Per the specification, browsers should re-evaluate media queries in response to changes in the user environment. This means that pointer, hover, any-pointer, and any-hover interaction media features can change dynamically at any point. For instance, adding/removing a bluetooth mouse on a mobile/tablet device will trigger a change in any-pointer / any-hover. A more drastic example would be a Surface tablet, where adding/removing the device’s “type cover” (which includes a keyboard and trackpad) will result in changes to the primary input itself (going from pointer: fine / hover: hover when the cover is present, to pointer: coarse / hover: none when the Surface is in “tablet mode”).

Screenshots of Firefox on a Surface tablet. With the cover attached, pointer:finehover:hoverany-pointer:coarseany-pointer:fine, and any-hover:hover are true; once the cover is removed (and Windows asks if the user wants to switch to “tablet mode”), touch becomes the primary input with pointer:coarse and hover:none, and only any-pointer:coarse and any-hover:none are true.

If you’re modifying your site’s layout/functionality based on these media features, be aware that the site may suddenly change “under the user’s feet” whenever the inputs change — not just when the page/site is first loaded.

Media queries may not be enough — roll on scripting

The fundamental shortcoming of the interaction media features is that they won’t necessarily tell us anything about the input devices that are in use right now. For that, we may need to dig deeper into solutions, like What Input?, that keep track of the specific JavaScript events fired. But of course, those solutions can only give us information about the user’s input after they have already started interacting with the site — at which point it may be too late to make drastic changes to your layout or functionality.

Keep in mind that even these JavaScript-based approaches can just as easily lead to incorrect results. That’s especially true on mobile/tablet platforms, or in situations where assistive technologies are involved, where it is common to see “faked” events being generated. For instance, if we look over the series of events fired when activating a control on desktop using a keyboard and screen reader, we can see that fake mouse events are triggered. Assistive technologies do this because, historically, a lot of web content has been coded to work for mouse users, but not necessarily for keyboard users, making a simulation of those interactions necessary for some functionalities.

Similarly, when activating “Full Keyboard Support” in iOS’s Settings → Accessibility → Keyboard, it’s possible for users to navigate web content using an external bluetooth keyboard, just as they would on desktop. But if we look at the event sequence for mobile/tablet devices and paired keyboard/mouse, that situation produces pointer events, touch events, and fallback mouse events — the same sequence we’d get for a touchscreen interaction.

Showing iOS settings with Full Keyboard Access enabled on the left and an iPhone browser window open to the right with the What Input tool.
When enabled, iOS’s “Full Keyboard Access” setting results in pointer, touch, and mouse events. What Input? identifies this as a touch input

In all these situations, scripts like What Input? will — understandably, through no fault of its own — misidentify the current input type.

Incorrect assumptions that can completely break the experience

Having outlined the complexity of multi-input devices, it should be clear by now that approaches that only listen to specific types of events, like the form of “touch detection” we see commonly in use, quickly fall apart.

if (window.matchMedia && window.matchMedia("(pointer: coarse)").matches) {   /* if the pointer is coarse, listen to touch events */   target.addEventListener("touchstart", ...);   // ... } else {   /* otherwise, listen to mouse and keyboard events */   target.addEventListener("click", ...);   // ... }

In the case of a “touch” device with additional inputs — such as a mobile or tablet with an external mouse — this code will essentially prevent the user from being able to use anything other than their touchscreen. And on devices that are primarily mouse-driven but do have a secondary touchscreen interface — like a Microsoft Surface — the user will be unable to use their touchscreen.

Instead of thinking about this as “touch or mouse/keyboard,” realize that it’s often a case of “touch and mouse/keyboard.” If we only want to register touch events when there’s an actual touchscreen device for performance reasons, we can try detecting any-pointer: coarse. But we should also keep other regular event listeners for mouse and keyboard.

/* always, as a matter of course, listen to mouse and keyboard events */ target.addEventListener("click", ...);  // ...  if (window.matchMedia && window.matchMedia("(any-pointer: coarse)").matches) {   /* if there's a coarse pointer, *also* listen to touch events */   target.addEventListener("touchstart", ...);   // ... }

Alternatively, we could avoid this entire conundrum about different types of events by using pointer events, which cover all types of pointer inputs in a single, unified event model, and are fairly well supported.

Give users an explicit choice

One potential solution for neatly circumventing our inability to make absolute determinations about which type of input the users are using may be to use the information provided by media queries and tools like What Input?, not to immediately switch between different layouts/functionalities — or worse, to only listen to particular types of events, and potentially locking out any additional input types — but to use them only as signals for when to provide users with an explicit way to switch modes.

For instance, see the way Microsoft Office lets you change between “Touch” and “Mouse” mode. On touch devices, this option is shown by default in the application’s toolbar, while on non-touch devices, it’s initially hidden (though it can be enabled, regardless of whether or not a touchscreen is present).

Screenshot of Microsoft Office's 'Touch/Mouse mode' dropdown, and a comparison of (part of) the toolbar as it's presented in each mode

A site or web application could take the same approach, and even set the default based on what the primary input is — but still allow users to explicitly change modes. And, using an approach similar to What Input?, the site could detect the first appearance of a touch-based input, and alert/prompt the user if they want to switch to a touch-friendly mode.

Potential for incorrect assumptions — query responsibly

Using Media Queries Level 4 Interaction Media Features and adapting our sites based on the characteristics of the available primary or additional pointer input is a great idea — but beware false assumptions about what these media features actually say. As with similar feature detection methods, developers need to be aware of what exactly they’re trying to detect, the limitations of that particular detection, and most importantly, consider why they are doing it — in a similar way to the problem I outlined in my article on detecting touch.

pointer and hover tell us about the capabilities of whatever the browser determines to be the primary device input. any-pointer and any-hover tell you about the capabilities of all connected inputs, and combined with information about the primary pointer input, they allow us to make educated guesses about a user’s particular device/scenario. We can use these features to inform our layout, or the type of interaction/functionality we want to offer; but don’t discount the possibility that those assumptions may be incorrect. The media queries themselves are not necessarily flawed (though the fact that most browsers seem to still have quirks and bugs adds to the potential problems). It just depends on how they’re used.

With that, I want to conclude by offering suggestions to “defend” yourself from the pitfalls of input detections.


Assume a single input type. It’s not “touch or mouse/keyboard” these days, but “touch and mouse/keyboard” — and the available input types may change at any moment, even after the initial page load.

Just go by pointer and hover. the “primary” pointer input is not necessarily the one that your users are using.

Rely on hover in general. Regardless of what hover or any-hover suggest, your users may have a pointer input that they’re currently using that is not hover-capable, and you can’t currently detect this unless it’s the primary input (since hover: none  is true if that particular input lacks hover, but any-hover: none will only ever be true if none of the inputs are hover-capable). And remember that hover-based interfaces generally don’t work for keyboard users.


Make your interfaces “touch-friendly.” If you detect that there’s an any-pointer:coarse input (most likely a touchscreen), consider providing large touch targets and sufficient spacing between them. Even if the user is using another input, like a mouse, at that moment, no harm done.

Give users a choice. If all else fails, consider giving the user an option/toggle to switch between touch or mouse layouts. Feel free to use any information you can glean from the media queries (such as any-pointer: coarse being true) to make an educated guess about the toggle’s initial setting.

Remember about keyboard users. Regardless of any pointer inputs that the user may or may not be using, don’t forget about keyboard accessibility — it can’t be conclusively detected, so just make sure your stuff works for keyboard users as a matter of course.

The post Interaction Media Features and Their Potential (for Incorrect Assumptions) appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , , , ,

Working with JavaScript Media Queries

What’s the first thing that comes to mind when you think of media queries? Maybe something in a CSS file that looks like this:

body {   background-color: plum; } 
 @media (min-width: 768px) {   body {     background-color: tomato;   } }

CSS media queries are a core ingredient in any responsive design. They’re a great way to apply different styles to different contexts, whether it’s based on viewport size, motion preference, preferred color scheme, specific interactions and, heck, even certain devices like printers, TVs and projectors, among many others.

But did you know that we have media queries for JavaScript too? It’s true! We may not see them as often in JavaScript, but there definitely are use cases for them I have found helpful over the years for creating responsive plugins, like sliders. For example, at a certain resolution, you may need to re-draw and recalculate the slider items.

Working with media queries in JavaScript is very different than working with them in CSS, even though the concepts are similar: match some conditions and apply some stuff.

Using matchMedia() 

To determine if the document matches the media query string in JavaScript, we use the matchMedia() method. Even though it’s officially part of the CSS Object Model View Module specification which is in Working Draft status, the browser support for it is great going as far back as Internet Explorer 10 with 98.6% global coverage.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.


Chrome Firefox IE Edge Safari
9 6 10 12 5.1

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
84 79 3 5.0-5.1

The usage is nearly identical to CSS media queries. We pass the media query string to matchMedia() and then check the .matches property.

// Define the query const mediaQuery = window.matchMedia('(min-width: 768px)')

The defined media query will return a MediaQueryList object. It is an object that stores information about the media query and the key property we need is .matches. That is a read-only Boolean property that returns true if the document matches the media query.

// Create a media condition that targets viewports at least 768px wide const mediaQuery = window.matchMedia('(min-width: 768px)') 
 // Check if the media query is true if (mediaQuery.matches) {   // Then trigger an alert   alert('Media Query Matched!') }

That’s the basic usage for matching media conditions in JavaScript. We create a match condition (matchMedia()) that returns an object (MediaQueryList), check against it (.matches), then do stuff if the condition evaluates to true. Not totally unlike CSS!

But there’s more to it. For example, if we were change the window size below our target window size, nothing updates the way it will with CSS right out of the box. That’s because .matches is perfect for one-time instantaneous checks but is unable to continuously check for changes. That means we need to…

Listen for changes

 MediaQueryList has an addListener() (and the subsequent removeListener()) method that accepts a callback function (represented by the .onchange event) that’s invoked when the media query status changes. In other words, we can fire additional functions when the conditions change, allowing us to “respond” to the updated conditions.

// Create a condition that targets viewports at least 768px wide const mediaQuery = window.matchMedia('(min-width: 768px)') 
 function handleTabletChange(e) {   // Check if the media query is true   if (e.matches) {     // Then log the following message to the console     console.log('Media Query Matched!')   } } 
 // Register event listener mediaQuery.addListener(handleTabletChange)  // Initial check handleTabletChange(mediaQuery)

The one-two punch of matchMedia() and MediaQueryList gives us the same power to not only match media conditions that CSS provides, but to actively respond to updated conditions as well.

When you register an event listener with addListener() it won’t fire initially. We need to call the event handler function manually and pass the media query as the argument.

The old way of doing things

For the sake of context — and a little nostalgia — I would like to cover the old, but still popular, way of doing “media queries” in JavaScript (and, yes, those quotes are important here). The most common approach is binding a resize event listener that checks window.innerWidth or window.innerHeight.

You’ll still see something like this in the wild:

function checkMediaQuery() {   // If the inner width of the window is greater then 768px   if (window.innerWidth > 768) {     // Then log this message to the console     console.log('Media Query Matched!')   } } 
 // Add a listener for when the window resizes window.addEventListener('resize', checkMediaQuery);

Since the resize event is called on each browser resize, this is an expensive operation! Looking at the performance impact of an empty page we can see the difference.

That’s a 157% increase in scripting!

An even simpler way to see the difference is with the help of a console log.

That’s 208 resize events versus six matched media events.

Even if we look past the performance issues, resize is restrictive in the sense that it doesn’t let us write advanced media queries for things like print and orientation. So, while it does mimic “media query” behavior by allowing us to match viewport widths, it’s incapable of matching much of anything else — and we know that true media queries are capable of so much more.


That’s a look at media queries in JavaScript! We explored how matchMedia() allows us to define media conditions and examined the MediaQueryList object that lets us do one-time (.matches) and persistent (addListener()) checks against those conditions so that we can respond to changes (.onchange) by invoking functions.

We also saw the “old” way of doing things by listening for resize events on the window. While it’s still widely used and a totally legit way to respond to changes to the size of the window.innerWidth, it’s unable to perform checks on advanced media conditions.

To finish the article here is a useful example that is not achievable in the old way. Using a media query I will check if the user is in the landscape mode. This approach is common when developing HTML5 games and is best viewed on a mobile device.

The post Working with JavaScript Media Queries appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , ,

Beyond Media Queries: Using Newer HTML & CSS Features for Responsive Designs

Beyond using media queries and modern CSS layouts, like flexbox and grid, to create responsive websites, there are certain overlooked things we can do well to make responsive sites. In this article, we’ll dig into a number tools (revolving around HTML and CSS) we have at the ready, from responsive images to relatively new CSS functions that work naturally whether we use media queries or not.

In fact, media queries become more of a complement when used with these features rather than the full approach. Let’s see how that works.

Truly responsive images

Remember when we could just chuck width: 100% on images and call it a day? That still works, of course, and does make images squishy, but there are a number of downsides that come with it, the most notable of which include:

  • The image might squish to the extent that it loses its focal point.
  • Smaller devices still wind up downloading the full size image.

When using images on the web, we have to make sure they’re optimized in terms of their resolution and size. The reason is to ensure that we have the right image resolution to fit the right device, so we don’t end up downloading really large and heavy images for smaller screens which could end up reducing the performance of a site. 

In simple terms, we’re making sure that larger, high-resolution images are sent to larger screens, while smaller, low-resolution variations are sent to smaller screens, improving both performance and user experience.

HTML offers the <picture> element that allows us specify the exact image resource that will be rendered based on the media query we add. As described earlier, instead of having one image (usually a large, high-resolution version) sent to all screen sizes and scaling it to the viewport width, we specify a set of images to serve in specific situations.

<picture>   <source media="(max-width:1000px)" srcset="picture-lg.png">   <source media="(max-width:600px)" srcset="picture-mid.png">   <source media="(max-width:400px)" srcset="picture-sm.png">   <img src="picture.png" alt="picture""> </picture>

In this example, picture.png is the full-size image. From there, we define the next-largest version of the image, picture-lg.png, and the size reduces in descending order until the smallest version, picture-sm.png. Note that we’re still using media queries in this approach, but it’s the <picture> element itself that is driving the responsive behavior rather than defining breakpoints in the CSS.

The media queries are added appropriately to scale with the sizes of the picture:

  • Viewports that are 1000px and above get picture.png.
  • Viewports that are between 601px and 999px get picture-lg.png.
  • Viewports that are between 401px and 600px get picture-sm.png.
  • Any thing smaller than 400px gets picture-sm.png.

Interestingly, we can also label each image by image density —  1x, 2x, 3x and so forth — after the URL. This works if we have made the different images in proportion to each other (which we did). This allows the browser to determine which version to download based on the screen’s pixel density in addition to the viewport size. But note how many images we wind up defining:

<picture>   <source media="(max-width:1000px)" srcset="picture-lg_1x.png 1x, picture-lg_2x.png 2x, picture-lg_3x.png 3x">   <source media="(max-width:600px)" srcset="picture-mid_1x.png 1x, picture-mid_2x.png 2x, picture-mid_3x.png 3x">   <source media="(max-width:400px)" srcset="picture-small_1x.png 1x, picture-small_2x.png 2x, picture-small_1x.png 3x">   <img src="picture.png" alt="picture""> </picture>

Let’s look specifically at the two tags nested inside the <picture> element: <source> and <img>.

The browser will look for the first <source> element where the media query matches the current viewport width, and then it will display the proper image (specified in the srcset attribute). The <img> element is required as the last child of the <picture> element, as a fallback option if none of the initial source tags matches.

We can also use image density to handle responsive images with just the <img> element using the srcset attribute:

<img  srcset="   flower4x.png 4x,   flower3x.png 3x,   flower2x.png 2x,   flower1x.png 1x  "  src="flower-fallback.jpg" >

Another thing we can do is write media queries in the CSS based on the screen resolution (usually measured in dots per inch, or dpi) of the device itself and not just the device viewport. What this means is that instead of:

@media only screen and (max-width: 600px) {   /* Style stuff */ }

We now have:

@media only screen and (min-resolution: 192dpi) {   /* Style stuff */ }

This approach lets us dictate what image to render based the screen resolution of the device itself, which could be helpful when dealing with high resolution images. Basically, that means we can display high quality pictures for screens that support higher resolutions and smaller versions at lower resolutions. It’s worth noting that, although mobile devices have small screens, they’re usually high resolution. That means it’s probably not the best idea rely on resolution alone when determining which image to render. It could result in serving large, high-resolution images to really small screens, which may not be the version we really want to display at such a small screen size.

body {   background-image : picture-md.png; /* the default image */ } 
 @media only screen and (min-resolution: 192dpi) {   body {     background-image : picture-lg.png; /* higher resolution */   } }

What <picture> gives us is basically the ability to art direct images. And, in keeping with this idea, we can leverage CSS features, like the object-fit property which, when used with object-position, allows us to crop images for better focal points while maintaining the image’s aspect ratio.

So, to change the focal point of an image:

@media only screen and (min-resolution: 192dpi) {   body {     background-image : picture-lg.png;     object-fit: cover;     object-position: 100% 150%; /* moves focus toward the middle-right */   } }

Setting minimum and maximum values in CSS

The min() function specifies the absolute smallest size that an element can shrink to. This function proves really useful in terms of helping text sizes to properly scale across different screen sizes, like never letting fluid type to drop below a legible font size:

html {   font-size: min(1rem, 22px); /* Stays between 16px and 22px */ }

min() accepts two values, and they can be relative, percentage, or fixed units. In this example, we’re telling the browser to never let an element with class .box go below 45% width or 600px, whichever is smallest based on the viewport width:

.box {   width : min(45%, 600px) }

If 45% computes to a value smaller than 600px, the browser uses 45% as the width. Conversely, if  45% computes to a value greater than 600px, then 600px will be used for the element’s width.

The same sort of thing goes for the max() function. It also accepts two values, but rather than specifying the smallest size for an element, we’re defining the largest it can get.

.box {   width : max(60%, 600px) }

If 60% computes to a value smaller than 600px, the browser uses 60% as the width. On the flip side, if 60% computes to a value greater than 600px, then 600px will be used as the element’s width.

And, hey, we can even set a minimum and maximum range instead using the minmax() function:

.box {   width : minmax( 600px, 50% ); /* at least 600px, but never more than 50% */ }

Clamping values

Many of us have been clamoring for clamp() for some time now, and we actually have broad support across all modern browsers (sorry, Internet Explorer). clamp() is the combination of the min() and max() functions, accepting three parameters:

  1. the minimum value,
  2. the preferred value, and
  3. the maximum value

For example:

.box {   font-size : clamp(1rem, 40px, 4rem) }

The browser will set the font at 1rem until the computed value of 1rem is larger than 40px. And when the computed value is above 40px? Yep, the browser will stop increasing the size after it hits 4rem. You can see how clamp() can be used to make elements fluid without reaching for media queries.

Working with responsive units

Have you ever built a page with a large heading or sub-heading and admired how great it looked on a desktop screen, only to check it on a mobile device and find out that’s it’s too large? I have definitely been in this situation and in this section I’ll be explaining how to handle such problems.

In CSS, you can determine sizes or lengths of elements using various units of measurements, and the most used units of measurements includes: px, em, rem, %, vw, and vh. Although, there are several more units that aren’t used as frequently. What’s of interest to us is that px can be considered an absolute unit, while the rest are considered relative units.

Absolute units

A pixel (px) is considered an absolute unit mainly because it’s fixed and does not change based on the measurement of any other element. It can be considered as the base, or root, unit that some other relative units use. Trying to use pixels for responsive behavior can bump into issues because it’s fixed, but they’re great if you have elements that should not be resized at all.

Relative units

Relative units, like %, em, and rem, are better suited to responsive design mainly because of their ability to scale across different screen sizes.

vw: Relative to the viewport’s width
vh: Relative to the viewport’s height
rem: Relative to the root (<html>) element (default font-size is usually 16px )
em: Relative to the parent element
%: Relative to the parent element

Again, the default font size for most browsers is 16px and and that’s what rem units use to generate their computed values. So, if a user adjusts the font size on the browser, everything on the page scales properly depending on the root size. For example, when dealing a root set at 16px, the number you specify will multiply that number times the default size. For example:

.8rem = 12.8px (.8 * 16) 1rem = 16px (1 * 16) 2rem = 32px (2 * 16)

What if either you or the user changes the default size? As we said already, these are relative units and the final size values will be based off of the new base size. This proves useful within media queries, where you just change the font size and the entire page scales up or down accordingly.

For example, if you changed the font-size to 10px within the CSS, then the calculated sizes would end up being:

html {   font-size : 10px; }
1rem = 10px (1 * 10) 2rem = 20px (2 * 10) .5rem = 5px (.5 * 10)

Note: This also applies to percentage %. For instance:

100% = 16px; 200% = 32px;  50% = 8px;

And what’s the difference between rem and em units? It’s what the unit uses as its base element. rem calculates values using the font size of the root (<html>) element, whereas an element declaring em values references the font size of the parent element that contains it. If the size of specified parent element is different from the root element (e.g. the parent elements is 18px but the root element is 16px) then em and rem will resolve to different computed values. This gives us more fine-grained control of how our elements respond in different responsive contexts.

vh is an acronym for viewport height, or the viewable screen’s height. 100vh represent 100% of the viewport’s height (depending on the device). In the same vein, vw stands for viewport width, meaning the viewable screen’s width of the device, and 100vw literally represents 100% of the viewport’s width.

Moving beyond media queries

See that? We just looked at a number of really powerful and relatively new HTML and CSS features that give us additional (and possible more effective) ways to build for responsiveness. It’s not that these new-fangled techniques replace what we’ve been doing all along. They are merely more tools in our developer tool belt that give us greater control to determine how elements behave in different contexts. Whether it’s working with font sizes, resolutions, widths, focal points, or any number of things, we have more fine-grain control of the user experience than ever before.

So, next time you find yourself working on a project where you wish you had more control over the exact look and feel of the design on specific devices, check out what native HTML and CSS can do to help — it’s incredible how far things have come along.

The post Beyond Media Queries: Using Newer HTML & CSS Features for Responsive Designs appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , , , , , ,

How to Make a Media Query-less Card Component

Fun fact: it’s possible to create responsive components without any media queries at all. Certainly, if we had container queries, those would be very useful for responsive design at the component level. But we don’t. Still, with or without container queries, we can do things to make our components surprisingly responsive. We’ll use concepts from Intrinsic Web Design, brought to us by Jen Simmons.

Let’s dive together into the use case described below, the solutions regarding the actual state of CSS, and some other tricks I’ll give you.

A responsive “Cooking Recipe” card

I recently tweeted a video and Pen of a responsive card demo I built using a recipe for pizza as an example. (It’s not important to the technology here, but I dropped the recipe at the end because it’s delicious and gluten free.)

The demo here was a first attempt based on a concept from one of Stéphanie Walter’s talks. Here is a video to show you how the card will behave:

And if you want to play with it right now, here’s the Pen.

Let’s define the responsive layout

A key to planning is knowing the actual content you are working, and the importance of those details. Not that we should be hiding content at any point, but for layout and design reasons, it’s good to know what needs to be communicated first and so forth. We’ll be displaying the same content no matter the size or shape of the layout.

Let’s imagine the content with a mobile-first mindset to help us focus on what’s most important. Then when the screen is larger, like on a desktop, we can use the additional space for things like glorious whitespace and larger typography. Usually, a little prioritization like this is enough to be sure of what content is needed for the cards at any and all viewport sizes.

Let’s take the example of a cooking recipe teaser:

In her talk, Stéphanie had already did the job and prioritized the content for our cards. Here’s what she outlined, in order of importance:

  1. Image: because it’s a recipe, you eat with your eyes!
  2. Title: to be sure what you’re going to cook.
  3. Keywords: to catch key info at the first glance.
  4. Rating info: for social proof.
  5. Short description: for the people who read.
  6. Call to action: what you expect the user to do on this card.

This may seem like a lot, but we can get all of that into a single smart card layout!

Non-scalable typography

One of the constraints with the technique I’m going to show you is that you won’t be able to get scalable typography based on container width. Scalable typography (e.g. “fluid type”) is commonly done with the with viewport width (vw) unit, which is based on the viewport, not the parent element.

So, while we might be tempted to reach for fluid type as a non-media query solution for the content in our cards, we won’t be able to use fluid type based on some percentage of the container width nor element width itself, unfortunately. That won’t stop us from our goal, however!

A quick note on “pixel perfection”

Let’s talk to both sides here…

Designers: Pixel perfect is super ideal, and we can certainly be precise at a component level. But there has to be some trade-off at the layout level. Meaning you will have to provide some variations, but allow the in-betweens to be flexible. Things shift in responsive layouts and precision at every possible screen width is a tough ask. We can still make things look great at every scale though!

Developers: You’ll have to be able to fill the gaps between the layouts that have prescribed designs to allow content to be readable and consistent between those states. As a good practice, I also recommend trying to keep as much of a natural flow as possible.

You can also read the Ahmad’s excellent article on the state of pixel perfection.

A recipe for zero media queries

Remember, what we’re striving for is not just a responsive card, but one that doesn’t rely on any media queries. It’s not that media queries should be avoided; it’s more about CSS being powerful and flexible enough for us to have other options available.

To build our responsive card, I was wondering if flexbox would be enough or if I would need to do it with CSS grid instead. Turns out flexbox in indeed enough for us this time, using the behavior and magic of the flex-wrap and flex-basis properties in CSS.

The gist of flex-wrap is that it allows elements to break onto a new line when the space for content gets too tight. You can see the difference between flex with a no-wrap value and with wrapping in this demo:

The flex-basis value of 200px is more of an instruction than a suggestion for the browser, but if the container doesn’t offer enough space for it, the elements move down onto a new line. The margin between columns even force the initial wrapping.

I used this wrapping logic to create the base of my card. Adam Argyle also used it on the following demo features four form layouts with a mere 10 lines of CSS:

In his example, Adam uses flex-basis and flex-grow (used together in flex shorthand property) )to allow the email input to take three times the space occupied by the name input or the button. When the browser estimates there is not enough rooms to display everything on the same row, the layout breaks itself into multiple lines by itself, without us having to manage the changes in media queries.

I also used clamp() function to add even more flexibility. This function is kind of magical. It allows us to resolve a min() and a max() calculation in a single function. The syntax goes like this:

clamp(MIN, VALUE, MAX)

It’s like resolving a combination of the max() and min() functions:

max(MIN, min(VAL, MAX))

You can use it for all kind of properties that cover:  <length>, <frequency>, <angle>, <time>, <percentage>, <number>, or <integer>.

The “No-Media Query Responsive Card” demo

With all of these new-fangled CSS powers, I created a flexible responsive card without any media queries. It might be best to view this demo in a new tab, or with a 0.5x option in the embed below.

Something you want to note right away is that the HTML code for the 2 cards are exactly the same, the only difference is that the first card is within a 65% wide container, and the second one within a 35% wide container. You can also play with the dimension of your window to test its responsiveness.

The important part of the code in that demo is on these selectors:

  • .recipe is the parent flex container.
  • .pizza-box is a flex item that is the container for the card image.
  • .recipe-content is a second flex item and is the container for the card content. 

Now that we know how flex-wrap works, and how flex-basis and flex-grow  influence the element sizing, we just need to quickly explain the clamp() function because I used it for responsive font sizing in place of where we may have normally reached for fluid type.

I wanted to use calc() and custom properties to calculate font sizes based on the width of the parent container, but I couldn’t find a way, as a 100% value has a different interpretation depending on the context. I kept it for the middle value of my clamp() function, but the end result was over-engineered and didn’t wind up working as I’d hoped or expected.

/* No need, really */ font-size: clamp(1.4em, calc(.5em * 2.1vw), 2.1em);

Here’s where I landed instead:

font-size: clamp(1.4em, 2.1vw, 2.1em);

That’s what I did to make the card title’s size adjust against the screen size but, like we discussed much earlier when talking about fluid type, we won’t be able to size the text by the parent container’s width.

Instead, we’re basically saying this with that one line of CSS:

I want the font-size to equal to 2.1vw (2.1% of the viewport width), but please don’t let it go below 1.4em or above 2.1em.

This maintains the title’s prioritized importance by allowing it to stay larger than the rest of the content, while keeping it readable. And, hey, it still makes grows and shrinks on the screen size!

And let’s not forget about responsive images, The content requirements say the image is the most important piece in the bunch, so we definitely need to account for it and make sure it looks great at all screen sizes. Now, you may want to do something like this and call it a day:

max-width: 100%; height: auto;

But that’s doesnt always result in the best rendering of an image. Instead, we have the object-fit property, which not only responds to the height and width of the image’s content-box, but allows us to crop the image and control how it stretches inside the box when used with the object-position property.

img {   max-width: 100%;   min-height: 100%;   width: auto;   height: auto;   object-fit: cover;   object-position: 50% 50%; }

As you can see, that is a lot of properties to write down. It’s mandatory because of the explicit width and height properties in the HTML <img> code. If you remove the HTML part (which I don’t recommend for performance reason) you can keep the object-* properties in CSS and remove the others.

An alternative recipe for no media queries

Another technique is to use flex-grow as a unit-based growing value, with an absurdly enormous value for flex-basis. The idea is stolen straight from the Heydon Pickering’s great “Holy Albatross” demo.

The interesting part of the code is this:

/* Container */ .recipe {   --modifier: calc(70ch - 100%); 
   display: flex;   flex-wrap: wrap; } 
 /* Image dimension */ .pizza-box {   flex-grow: 3;   flex-shrink: 1;   flex-basis: calc(var(--modifier) * 999); } 
 /* Text content dimension */ .recipe-content {   flex-grow: 4;   flex-shrink: 1;   flex-basis: calc(var(--modifier) * 999); }

Proportional dimensions are created by flex-grow while the flex-basis dimension can be either invalid or extremely high. The value gets extremely high when calc(70ch - 100%), the value of  --modifier, reaches a positive value. When the values are extremely high each of them fills the space creating a column layout; when the values are invalid, they lay out inline.

The value of 70ch acts like the breakpoint in the recipe component (almost like a container query). Change it depending on your needs.

Let’s break down the ingredients once again

Here are the CSS ingredients we used for a media-query-less card component:

  • The clamp() function helps resolve a “preferred” vs. “minimum” vs. “maximum” value.
  • The flex-basis property with a negative value decides when the layout breaks into multiple lines.
  • The flex-grow property is used as a unit value for proportional growth.
  • The vw unit helps with responsive typography.
  • The  object-fit property provides finer responsiveness for the card image, as it allows us to alter the dimensions of the image without distorting it.

Going further with quantity queries

I’ve got another trick for you: we can adjust the layout depending on the number of items in the container. That’s not really a responsiveness brought by the dimension of a container, but more by the context where the content lays.

There is no actual media query for number of items. It’s a little CSS trick to reverse-count the number of items and apply style modifications accordingly.

The demo uses the following selector:

.container > :nth-last-child(n+3), .container > :nth-last-child(n+3) ~ * {   flex-direction: column; }

Looks tricky, right? This selector allows us to apply styles from the last-child and all it’s siblings. Neat! 

Una Kravets explains this concept really well. We can translate this specific usage like this:

  • .container > :nth-last-child(n+3): The third .container element or greater from the last .container in the group.
  • .container > :nth-last-child(n+3) ~ *: The same exact thing, but selects any .container element after the last one. This helps account for any other cards we add.

Hugo Giraudel’s “Selectors Explained” tool really helps translate complex selectors into plain English, if you’d like another translation of how these selectors work.

Another way to get “quantity” containers in CSS is to use binary conditions. But the syntax is not easy and seems a bit hacky. You can reach me on Twitter if you need to talk about that — or any other tricks and tips about CSS or design. pastedGraphic.png

Is this future proof?

All the techniques I presented you here can be used today in a production environment. They’re well supported and offer opportunities for graceful degradation.

Worst case scenario? Some unsupported browser, say Internet Explorer 9, won’t change the layout based on the conditions we specify, but the content will still be readable. So, it’s supported, but might not be “optimized” for the ideal experience.

Maybe one day we will finally get see the holy grail of container queries in the wild. Hopefully the Intrinsic Web Design patterns we’ve used here resonate with you and help you build flexible and “intrinsicly-responsive” components in the meantime.

Let’s get to the “rea” reason for this post… the pizza! 🍕

Gluten free pan pizza recipe

You can pick the toppings. The important part is the dough, and here is that:


  • 3¼ cups (455g) gluten free flour
  • 1 tablespoon, plus 1 teaspoon (29g) brown sugar
  • 2 teaspoons of kosher salt
  • 1/2 cube of yeast
  • 2½ cups (400 ml) whole almond milk
  • 4 tablespoons of melted margarine
  • 1 tablespoon of maizena


  1. Mix all the dry ingredients together.
  2. Add the liquids.
  3. Let it double size for 2 hours. I’d recommend putting a wet dish towel over your bowl where the dough is, and place the dish close to a hot area (but not too hot because we don’t want it to cook right this second).
  4. Put it in the pan with oil. Let it double size for approximately 1 hour.
  5. Cook in the oven at 250 degrees for 20 minutes.

Thanks Stéphanie for the recipe 😁

The post How to Make a Media Query-less Card Component appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , ,

Comparing Social Media Outlets for Developer Tips

As a little experiment, I shared a development tip on three different social networks. I also tried to post it in a format that was most suitable for that particular social network:

How did each of them “do”? Let’s take a look. But bear in mind… this ain’t scientific. This is just me having a glance at one isolated example to get a feel for things across different social media sites.

The Twitter Thread

The Tweet

Twitter is probably our largest social media outlet. Despite the fact that I’ve done absolutely nothing with it this year other than auto-tweeting posts from this site (via our Jetpack Integration), those tweets do just about as well as it ever did when I was writing each tweet. These numbers are bound to change, but at the time of writing:










428 (first tweet)

Tweet Analytics showing 102,501 Impressions, 3,753 engagements and a few other more fine-grained stats.
Twitter provides analytics on tweets

Going off that engagements number, a little bit less than 1% of the followers had anything to do with it. I’d say this was a very average tweet for us, if not on the low side.

The Instagram Post

The Post

Instagram is by far the smallest of our social media outlets, being newer and not something I stay particularly active or consistent on. No auto-posting there just yet.







Instagram provides analytics (“insights”) on posts.

Using Reach, that’s 96% of the followers. That’s pretty incredible compared t 1% of followers on Twitter. Although, on Twitter. I can easily put URLs to tweets and send people places, where my only options on Instagram are “check out the link in my profile” or use a swipe-up thing in an Instagram Story. So, despite the high engagement of Instagram, I’m mostly just getting the satisfaction of teaching something as well as a little brand awareness. It’s much harder for me to get you to directly do something from Instagram.

The YouTube Video

The Video

YouTube is in the middle for us, much bigger than Instagram but not as big as Twitter. YouTube is a little unique in that there can be (and are) advertising directly on the videos and that get’s a “revenue share” from YouTube. That’s very much not driving motivation for using YouTube (I make 50 cents a day, but it is unique compared to the others.







YouTube analytics page showing 2.4K views, 192.8 hours of watch time, and a chart showing a graph that this video has more views than typical over time.
YouTube provides video analytics


We do have a Facebook page but it’s the most neglected of all of them. We auto-post new articles to it, but this experiment didn’t really have a blog post. I published the video to our site, but that doesn’t get auto-posted to Facebook, so the tip never made it there.

I used to feel a little guilty about not taking as much advantage of Facebook as I could, but whenever I look at overall analytics, I’m reminded that all of social media accounts combine for ~2% of traffic to this site. Spending any more time on this stuff is foolish for me, when that time could be spent on content for this site and information architecture for what we already have. And for Facebook specifically, whatever time we have spent there has never seemed to pan out. Just not a hive for developers.


I probably should have factored CodePen into this more, since it’s something of a social network itself with similar metrics. I worked on the examples in CodePen and the whole video was done in CodePen. But in this case, it was more about the journey than the destination. I did ultimately link to a demo at the end of the Twitter thread, but Instagram can’t link to it and I wasn’t as compelled to link to it on YouTube as the video itself to me was the important information.

If I was trying to compare CodePen stats here, I would have created the Pen in a step-by-step educational format so I could deliver the same idea. That actually sounds fun and I should probably still do that!



The problem is that there isn’t anything particularly useful to measure. What would have been way more interesting is if I had some really important call to action in each one where I’m like trying to sell you something or get you to sign up for something or whatever. I feel like that’s the real world of developer marketing. You gotta do 100 things for someone for free if you want them to do something for you on that 101st time. And on the 101st time, you should probably measure it somehow to see if the effort is worth it.

Here’s the very basic data together though…

Followers Engagements %
Twitter ~446,000 3,753 0.08%
Instagram ~2,800 2,685 96%
YouTube ~51,300 2,455 5%

One interesting thing is that I find the effort was about equal for all of them. You’d think a video would be hardest, but at least that’s just hit-record-hit-stop and minor editing. The other formats take longer to craft with custom text and graphics.

These would be my takeaways from this limited experiment:

  • You need big numbers on Twitter to do much. That’s because the engagement is pretty low. Still, it’s probably our best outlet for getting people to click a link and do something.
  • Instagram has amazing engagement, but it’s hard to send anyone anywhere. It’s still no wonder why people use it. You really do reach your audience there. If you had a strong call to action, I bet you could still get people do to it even with the absence of links (since people know how to search for stuff on the web).
  • While I mentioned that for this example the effort level was fairly even, in general, YouTube is going to require much higher effort. Video production just isn’t the same as farting out a couple of words or a screenshot. With that, and knowing that you’d need absolutely massive numbers to earn anything directly from YouTube, it’s pretty similar to other social networks in that you need to derive value from it abstractly.
  • This was not an idea that “went viral” in any sense. This is just standard-grade engagement, which was good for this experiment. I’m always super surprised at the type of developer tips that go viral. It’s always something I don’t expect, and often something I’m like awwwww we have an article about that too! I’d never bet on or expect anything going viral. Making stuff that your normal audience likes is the ticket.
  • Being active is pretty important. Any chart I’ve seen has big peaks when posts go out regularly and valleys when they don’t. Post regularly = riding the peaks.
  • None of this compares anywhere close to the real jewel of making things: blogging. Blogging is where you have full control and full benefit. The most important thing social media can do is get people over to your own site.

The post Comparing Social Media Outlets for Developer Tips appeared first on CSS-Tricks.


, , , , ,

Solving Sticky Hover States with @media (hover: hover)

Mezo Istvan does a good job of covering the problem and a solution to it in a blog post on Medium¹.

If you tap on something that has a :hover state but you don’t leave the page then, on a mobile device, there is a chance that :hover state “sticks.” You’ll see this with stuff like jump-links used as tabs or buttons that trigger on-page functionality.

button:hover {   border: 3px solid green; /* might stick! */ }

The solution, or trick, is a new(ish) “CSS4” media query that allows you only to apply styles on devices with hover capability.

@media (hover: hover) {   button:hover {     border: 3px solid green; /* solves sticky problem */   } }

Your typical touch screen mobile device will fail that media query, the style won’t apply, and you’ll avoid the sticky problem.

Support is solid, so not much worry there.

  1. It almost feels like we have to apologize to linking to things on Medium lately. I have no idea what you’re going to experience when you get there. Will you just be able to read it? Will it be a teaser where you have to log in to read more? Will it be behind a paywall? I have no idea. In this case, hopefully, this link post has enough info in it that isn’t not blocking you from learning anything.

Direct Link to ArticlePermalink

The post Solving Sticky Hover States with @media (hover: hover) appeared first on CSS-Tricks.


, , , ,