media queries – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Mon, 31 Oct 2022 13:05:39 +0000 en-US hourly 1 https://wordpress.org/?v=6.1.1 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 media queries – CSS-Tricks https://css-tricks.com 32 32 45537868 The New CSS Media Query Range Syntax https://css-tricks.com/the-new-css-media-query-range-syntax/ https://css-tricks.com/the-new-css-media-query-range-syntax/#comments Mon, 31 Oct 2022 13:05:38 +0000 https://css-tricks.com/?p=374670 The Media Queries Level 4 specification has introduced a new syntax for targeting a range of viewport widths using common mathematical comparison operators, like <, >, and =, that make more sense syntactically while writing less code for responsive web design.


The New CSS Media Query Range Syntax originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
We rely on CSS Media Queries for selecting and styling elements based on a targeted condition. That condition can be all kinds of things but typically fall into two camps: (1) the type of media that’s being used, and (2) a specific feature of the browser, device, or even the user’s environment.

So, say we want to apply certain CSS styling to a printed document:

@media print {
  .element {
    /* Style away! */
  }
}

The fact that we can apply styles at a certain viewport width has made CSS Media Queries a core ingredient of responsive web design since Ethan Marcotte coined the term. If the browser’s viewport width is a certain size, then apply a set of style rules, which allows us to design elements that respond to the size of the browser.

/* When the viewport width is at least 30em... */
@media screen and (min-width: 30em) {
  .element {
    /* Style away! */
  }
}

Notice the and in there? That’s an operator that allows us to combine statements. In that example, we combined a condition that the media type is a screen and that it’s min-width feature is set to 30em (or above). We can do the same thing to target a range of viewport sizes:

/* When the viewport width is between 30em - 80em */
@media screen and (min-width: 30em) and (max-width: 80em) {
  .element {
    /* Style away! */
  }
}

Now those styles apply to an explicit range of viewport widths rather than a single width!

But the Media Queries Level 4 specification has introduced a new syntax for targeting a range of viewport widths using common mathematical comparison operators — things like <, >, and = — that make more sense syntactically while writing less code.

Let’s dig into how that works.

New comparison operators

That last example is a good illustration of how we’ve sort of “faked” ranges by combining conditions using the and operator. The big change in the Media Queries Level 4 specification is that we have new operators that compare values rather than combining them:

  • < evaluates if a value is less than another value
  • > evaluates if a value is greater than another value
  • = evaluates if a value is equal to another value
  • <= evaluates if a value is less than or equal to another value
  • >= evaluates if a value is greater than or equal to another value

Here’s how we might’ve written a media query that applies styles if the browser is 600px wide or greater:

@media (min-width: 600px) {
  .element {
    /* Style away! */
  }
}

Here’s how it looks to write the same thing using a comparison operator:

@media (width >= 600px) {
  .element {
    /* Style away! */
  }
}

Targeting a range of viewport widths

Often when we write CSS Media Queries, we’re creating what’s called a breakpoint — a condition where the design “breaks” and a set of styles are applied to fix it. A design can have a bunch of breakpoints! And they’re usually based on the viewport being between two widths: where the breakpoint starts and where the breakpoint ends.

Here’s how we’ve done that using the and operator to combine the two breakpoint values:

/* When the browser is between 400px - 1000px */
@media (min-width: 400px) and (max-width: 1000px) {
  /* etc. */
}

You start to get a good sense of how much shorter and easier it is to write a media query when we ditch the Boolean and operator in favor of the new range comparison syntax:

@media (400px <= width <= 1000px) {
  /* etc. */
}

Much easier, right? And it’s clear exactly what this media query is doing.

Browser support

This improved media query syntax is still in its early days at the time of this writing and not as widely supported at the moment as the approach that combines min-width and max-width. We’re getting close, though! Safari is the only major holdout at this point, but there is an open ticket for it that you can follow.

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

Desktop

ChromeFirefoxIEEdgeSafari
10463No104TP

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
108107108No

Let’s look at an example

Here’s a layout for that’s nicely suited for larger screens, like a desktop:

A desktop layout with a logo and menu up top, a large heading in white, and an image of a silhouetted person underneath the heading, followed by a footer.

This layout has base styles that are common to all breakpoints. But as the screen gets narrower, we start to apply styles that are conditionally applied at different smaller breakpoints that are ideally suited for tablets all the way down to mobile phones:

Side-by-side screenshots of the mobile and tablet layouts with their CSS Grid tracks overlaid.

To see what’s happening, here’s a how the layout responds between the two smaller breakpoints. The hidden nav list getting displayed as well as title in the main gets increased in font-size.

That change is triggered when the viewport’s changes go from matching one media’s conditions to another:

/* Base styles (any screen size) */
header {
  display: flex;
  justify-content: center;
}

header ul {
  display: none;
}

.title p {
  font-size: 3.75rem;
}

/* When the media type is a screen with a width greater or equal to 768px */
@media screen and (width >= 768px) {
  header {
    justify-content: space-between;
  }

  header ul {
    display: flex;
    justify-content: space-between;
    gap: 3rem;
  }

  .title p {
    font-size: 5.75rem;
  }
}

We’ve combined a few of the concepts we’ve covered! We’re targeting devices with a screen media type, evaluating whether the viewport width is greater than or equal to a specific value using the new media feature range syntax, and combining the two conditions with the and operator.

Diagram of the media query syntax, detailing the media type, operator, and range media feature.

OK, so that’s great for mobile devices below 768px and for other devices equal to or greater than 768px. But what about that desktop layout… how do we get there?

As far as the layout goes:

  • The main element becomes a 12-column grid.
  • A button is displayed on the image.
  • The size of the .title element’s font increases and overlaps the image.

Assuming we’ve done our homework and determined exactly where those changes should take place, we can apply those styles when the viewport matches the width condition for that breakpoint. We’re going to say that breakpoint is at 1000px:

/* When the media type is a screen with a width greater or equal to 1000px  */
@media screen and (width >= 1000px) {
  /* Becomes a 12-column grid */
  main {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    grid-template-rows: auto 250px;
  }

  /* Places the .title on the grid */
  .title {
    grid-row: 1;
  }

  /* Bumps up the font-size */
  .title p {
    font-size: 7.75rem;
  }

  /* Places .images on the grid */
  .images {
    grid-row: 1 / span 2;
    align-self: end;
    position: relative;
  }

  /* Displays the button */
  .images .button {
    display: block;
    position: absolute;
    inset-block-end: 5rem;
    inset-inline-end: -1rem;
  }
}
Showing the CSS grid tracks for a desktop layout using a CSS media query with the new range syntax.

Have a play with it:

Why the new syntax is easier to understand

The bottom line: it’s easier to distinguish a comparison operator (e.g. width >= 320px) than it is to tell the difference between min-width and max-width using the and operator. By removing the nuance between min- and max-, we have one single width parameter to work with and the operators tell us the rest.

Beyond the visual differences of those syntaxes, they are also doing slightly different things. Using min- and max- is equivalent to using mathematical comparison operators:

  • max-width is equivalent to the <= operator (e.g. (max-width: 320px) is the same as (width <= 320px)).
  • min-width is equivalent to the >= operator (e.g. (min-width: 320px) is the same as (width >= 320px)).

Notice that neither is the equivalent of the > or < operators.

Let’s pull an example straight from the Media Queries Level 4 specification where we define different styles based on a breakpoint at 320px in the viewport width using min-width and max-width:

@media (max-width: 320px) { /* styles for viewports <= 320px */ }
@media (min-width: 320px) { /* styles for viewports >= 320px */ }

Both media queries match a condition when the viewport width is equal to 320px. That’s not exactly what we want. We want either one of those conditions rather than both at the same time. To avoid that implicit changes, we might add a pixel to the query based on min-width:

@media (max-width: 320px){ /* styles for viewports <= 320px */ }
@media (min-width: 321px){ /* styles for viewports >= 321px */ }

While this ensures that the two sets of styles don’t apply simultaneously when the viewport width is 320px, any viewport width that fall between 320px and 321px will result in a super small zone where none of the styles in either query are applied — a weird “flash of unstyled content” situation.

One solution is to increase the second comparison scale value (numbers after the decimal point) to 320.01px:

@media (max-width: 320px) { /* styles for viewports <= 320px */ }
@media (min-width: 320.01px) { /* styles for viewports >= 320.01px */ }

But that’s getting silly and overly complicated. That’s why the new media feature range syntax is a more appropriate approach:

@media (width <= 320px) { /* styles for viewports <= 320px */ }
@media (width > 320px) { /* styles for viewports > 320px */ }

Wrapping up

Phew, we covered a lot of ground on the new syntax for targeting viewport width ranges in CSS Media Queries. Now that the Media Queries Level 4 specification has introduced the syntax and it’s been adopted in Firefox and Chromium browsers, we’re getting close to being able to use the new comparison operators and combining them with other range media features besides width, like height and aspect-ratio

And that’s just one of the newer features that the Level 4 specification introduced, alongside a bunch of queries we can make based on user preferences. It doesn’t end there! Check out the Complete Guide to CSS Media Queries for a sneak peek of what might be included in Media Queries Level 5.


The New CSS Media Query Range Syntax originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/the-new-css-media-query-range-syntax/feed/ 11 374670
Responsive Layouts, Fewer Media Queries https://css-tricks.com/responsive-layouts-fewer-media-queries/ https://css-tricks.com/responsive-layouts-fewer-media-queries/#comments Mon, 22 Nov 2021 15:29:03 +0000 https://css-tricks.com/?p=357080 We cannot talk about web development without talking about Responsive Design. It’s just a given these days and has been for many years. Media queries are a part of Responsive Design and they aren’t going anywhere. Since the introduction of …


Responsive Layouts, Fewer Media Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
We cannot talk about web development without talking about Responsive Design. It’s just a given these days and has been for many years. Media queries are a part of Responsive Design and they aren’t going anywhere. Since the introduction of media queries (literally decades ago), CSS has evolved to the points that there are a lot of tricks that can help us drastically reduce the usage of media queries we use. In some cases, I will show you how to replace multiple media queries with only one CSS declaration. These approaches can result in less code, be easier to maintain, and be more tied to the content at hand.

Let’s first take a look at some widely used methods to build responsive layouts without media queries. No surprises here — these methods are related to flexbox and grid.

Using flex and flex-wrap

Live Demo

In the above demo, flex: 400px sets a base width for each element in the grid that is equal to 400px. Each element wraps to a new line if there isn’t enough room on the currently line to hold it. Meanwhile, the elements on each line grow/stretch to fill any remaining space in the container that’s leftover if the line cannot fit another 400px element, and they shrink back down as far as 400px if another 400px element can indeed squeeze in there.

Let’s also remember that flex: 400px is a shorthand equivalent to flex: 1 1 400px (flex-grow: 1, flex-shrink: 1, flex-basis: 400px).

What we have so far:

  • ✔️ Only two lines of code
  • ❌ Consistent element widths in the footer
  • ❌ Control the number of items per row
  • ❌ Control when the items wrap

Using auto-fit and minmax

Live Demo

Similar to the previous method, we are setting a base width—thanks to repeat(auto-fit, minmax(400px, 1fr))—and our items wrap if there’s enough space for them. This time, though we’re reaching for CSS Grid. That means the elements on each line also grow to fill any remaining space, but unlike the flexbox configuration, the last row maintains the same width as the rest of the elements.

So, we improved one of requirements and solved another, but also introduced a new issue since our items cannot shrink below 400px which may lead to some overflow.

  • ✔️ Only one line of code
  • ✔️ Consistent element widths in the footer
  • ❌ Control the number of items per row
  • ❌ Items grow, but do not shrink
  • ❌ Control when the items wrap

Both of the techniques we just looked at are good, but we also now see they come with a few drawbacks. But we can overcome those with some CSS trickery.

Control the number of items per row

Let’s take our first example and change flex: 400px to flex: max(400px, 100%/3 - 20px).

Resize the screen and notice that each row never has more than three items, even on a super wide screen. We have limited each line to a maximum of three elements, meaning each line only contains between one and three items at any given time.

Let’s dissect the code:

  • When the screen width increases, the width of our container also increases, meaning that 100%/3 gets bigger than 400px at some point.
  • Since we are using the max() function as the width and are dividing 100% by 3 in it, the largest any single element can be is just one-third of the overall container width. So, we get a maximum of three elements per row.
  • When the screen width is small, 400px takes the lead and we get our initial behavior.

You might also be asking: What the heck is that 20px value in the formula?

It’s twice the grid template’s gap value, which is 10px times two. When we have three items on a row, there are two gaps between elements (one on each on the left and right sides of the middle element), so for N items we should use max(400px, 100%/N - (N - 1) * gap). Yes, we need to account for the gap when defining the width, but don’t worry, we can still optimize the formula to remove it!

We can use max(400px, 100%/(N + 1) + 0.1%). The logic is: we tell the browser that each item has a width equal to 100%/(N + 1) so N + 1 items per row, but we add a tiny percentage ( 0.1%)—thus one of the items wraps and we end with only N items per row. Clever, right? No more worrying about the gap!

Now we can control the maximum number of items per row which give us a partial control over the number of items per row.

The same can also be applied to the CSS Grid method as well:

Note that here I have introduced custom properties to control the different values.

We’re getting closer!

  • ✔️ Only one line of code
  • ✔️ Consistent element widths in the footer
  • ⚠️ Partial control of the number of items per row
  • ❌ Items grow, but do not shrink
  • ❌ Control when the items wrap

Items grow, but do not shrink

We noted earlier that using the grid method could lead to overflow if the base width is bigger than the container width. To overcome this we change:

max(400px, 100%/(N + 1) + 0.1%)

…to:

clamp(100%/(N + 1) + 0.1%, 400px, 100%)

Breaking this down:

  • When the screen width is big, 400px is clamped to 100%/(N + 1) + 0.1%, maintaining our control of the maximum number of items per row.
  • When the screen width is small, 400px is clamped to 100% so our items never exceed the container width.

We’re getting even closer!

  • ✔️ Only one line of code
  • ✔️ Consistent element widths in the footer
  • ⚠️ Partial control of the number of items per row
  • ✔️ Items grow and shrink
  • ❌ Control when the items wrap

Control when the items wrap

So far, we’ve had no control over when elements wrap from one line to another. We don’t really know when it happens because it depends a number of things, like the base width, the gap, the container width, etc. To control this, we are going to change our last clamp() formula, from this:

clamp(100%/(N + 1) + 0.1%, 400px, 100%)

…to:

clamp(100%/(N + 1) + 0.1%, (400px - 100vw)*1000, 100%)

I can hear you screaming about that crazy-looking math, but bear with me. It’s easier than you might think. Here’s what’s happening:

  • When the screen width (100vw) is greater than 400px, (400px - 100vw) results in a negative value, and it gets clamped down to 100%/(N + 1) + 0.1%, which is a positive value. This gives us N items per row.
  • When the screen width (100vw) is less than 400px, (400px - 100vw) is a positive value and multiplied by a big value that’s clamped to the 100%. This results in one full-width element per row.
Live Demo

Hey, we made our first media query without a real media query! We are updating the number of items per row from N to 1 thanks to our clamp() formula. It should be noted that 400px behave as a breakpoint in this case.

What about: from N items per row to M items per row?

We can totally do that by updating our container’s clamped width:

clamp(100%/(N + 1) + 0.1%, (400px - 100vw)*1000, 100%/(M + 1) + 0.1%)

I think you probably get the trick by now. When the screen width is bigger than 400px we fall into the first rule (N items per row). When the screen width is smaller than 400px, we fall into the second rule (M items per row).

Live Demo

There we go! We can now control the number of items per row and when that number should change—using only CSS custom properties and one CSS declaration.

  • ✔️ Only one line of code
  • ✔️ Consistent element widths in the footer
  • ✔️ Full control of the number of items per row
  • ✔️ Items grow and shrink
  • ✔️ Control when the items wrap

More examples!

Controlling the number of items between two values is good, but doing it for multiple values is even better! Let’s try going from N items per row to M items per row, down to one item pre row.

Our formula becomes:

clamp(clamp(100%/(N + 1) + 0.1%, (W1 - 100vw)*1000,100%/(M + 1) + 0.1%), (W2 - 100vw)*1000, 100%)

A clamp() within a clamp()? Yes, it starts to get a big lengthy and confusing but still easy to understand. Notice the W1 and W2 variables in there. Since we are changing the number of items per rows between three values, we need two “breakpoints” (from N to M, and from M to 1).

Here’s what’s happening:

  • When the screen width is smaller than W2, we clamp to 100%, or one item per row.
  • When the screen width is larger than W2, we clamp to the first clamp().
  • In that first clamp, when the screen width is smaller than W1, we clamp to 100%/(M + 1) + 0.1%), or M items per row.
  • In that first clamp, when the screen width is bigger than W1, we clamp to 100%/(N + 1) + 0.1%), or N items per row.

We made two media queries using only one CSS declaration! Not only this, but we can adjust that declaration thanks to the CSS custom properties, which means we can have different breakpoints and a different number of columns for different containers.

How many media queries do we have in the above example? Too many to count but we will not stop there. We can have even more by nesting another clamp() to get from N columns to M columns to P columns to one column. (😱)

clamp(
  clamp(
    clamp(100%/(var(--n) + 1) + 0.1%, (var(--w1) - 100vw)*1000,
          100%/(var(--m) + 1) + 0.1%),(var(--w2) - 100vw)*1000,
          100%/(var(--p) + 1) + 0.1%),(var(--w3) - 100vw)*1000,
          100%), 1fr))
from N columns to M columns to P columns to 1 column

As I mentioned at the very beginning of this article, we have a responsive layout without any single media queries while using just one CSS declaration—sure, it’s a lengthy declaration, but still counts as one.

A small summary of what we have:

  • ✔️ Only one line of code
  • ✔️ Consistent element widths in the footer
  • ✔️ Full control of the number of items per row
  • ✔️ Items grow and shrink
  • ✔️ Control when the items wrap
  • ✔️ Easy to update using CSS custom properties

Let’s simulate container queries

Everyone is excited about container queries! What makes them neat is they consider the width of the element instead of the viewport/screen. The idea is that an element can adapt based on the width of its parent container for more fine-grain control over how elements respond to different contexts.

Container queries aren’t officially supported anywhere at the time of this writing, but we can certainly mimic them with our strategy. If we change 100vw with 100% throughout the code, things are based on the .container element’s width instead of the viewport width. As simple as that!

Resize the below containers and see the magic in play:

The number of columns change based on the container width which means we are simulating container queries! We’re basically doing that just by changing viewport units for a relative percentage value.

More tricks!

Now that we can control the number of columns, let’s explore more tricks that allow us to create conditional CSS based on either the screen size (or the element size).

Conditional background color

A while ago someone on StackOverflow asked if it is possible to change the color of an element based on its width or height. Many said that it’s impossible or that it would require a media query.

But I have found a trick to do it without a media query:

div {
  background:
   linear-gradient(green 0 0) 0 / max(0px,100px - 100%) 1px,
   red;
}
  • We have a linear gradient layer with a width equal to max(0px,100px - 100%) and a height equal to 1px. The height doesn’t really matter since the gradient repeats by default. Plus, it’s a one color gradient, so any height will do the job.
  • 100% refers to the element’s width. If 100% computes to a value bigger than 100px, the max() gives us 0px, which means that the gradient does not show, but the comma-separated red background does.
  • If 100% computes to a value smaller than 100px, the gradient does show and we get a green background instead.

In other words, we made a condition based on the width of the element compared to 100px!

This demo supports Chrome, Edge, and Firefox at the time of writing.

The same logic can be based on an element’s height instead by rearranging where that 1px value goes: 1px max(0px,100px - 100%). We can also consider the screen dimension by using vh or vw instead of %. We can even have more than two colors by adding more gradient layers.

div {
  background:
   linear-gradient(purple 0 0) 0 /max(0px,100px - 100%) 1px,
   linear-gradient(blue   0 0) 0 /max(0px,300px - 100%) 1px,
   linear-gradient(green  0 0) 0 /max(0px,500px - 100%) 1px,
   red;
}

Toggling an element’s visibility

To show/hide an element based on the screen size, we generally reach for a media query and plop a classic display: none in there. Here is another idea that simulates the same behavior, only without a media query:

div {
  max-width: clamp(0px, (100vw - 500px) * 1000, 100%);
  max-height: clamp(0px, (100vw - 500px) * 1000, 1000px);
  overflow: hidden;
}

Based on the screen width (100vw), we either get clamped to a 0px value for the max-height and max-width (meaning the element is hidden) or get clamped to 100% (meaning the element is visible and never greater than full width). We’re avoiding using a percentage for the max-height since it fails. That’s why we’re using a big pixel value (1000px).

Notice how the green elements disappear on small screens:

It should be noted that this method is not equivalent to the toggle of the display value. It’s more of a trick to give the element 0×0 dimensions, making it invisible. It may not be suitable for all cases, so use it with caution! It’s more a trick to be used with decorative elements where we won’t have accessibility issues. Chris wrote about how to hide content responsibly.

It’s important to note that I am using 0px and not 0 inside clamp() and max(). The latter makes invalidates property. I won’t dig into this but I have answered a Stack Overflow question related to this quirk if you want more detail.

Changing the position of an element

The following trick is useful when we deal with a fixed or absolutely positioned element. The difference here is that we need to update the position based on the screen width. Like the previous trick, we still rely on clamp() and a formula that looks like this: clamp(X1,(100vw - W)*1000, X2).

Basically, we are going to switch between the X1 and X2 values based on the difference, 100vw - W, where W is the width that simulates our breakpoint.

Let’s take an example. We want a div placed on the left edge (top: 50%; left:0) when the screen size is smaller than 400px, and place it somewhere else (say top: 10%; left: 40%) otherwise.

div {
  --c:(100vw - 400px); /* we define our condition */
  top: clamp(10%, var(--c) * -1000, 50%);
  left: clamp(0px, var(--c) * 1000, 40%);
}
Live Demo

First, I have defined the condition with a CSS custom property to avoid the repetition. Note that I also used it with the background color switching trick we saw earlier—we can either use (100vw - 400px) or (400px - 100vw), but pay attention to the calculation later as both don’t have the same sign.

Then, within each clamp(), we always start with the smallest value for each property. Don’t incorrectly assume that we need to put the small screen value first!

Finally, we define the sign for each condition. I picked (100vw - 400px), which means that this value will be negative when the screen width is smaller than 400px, and positive when the screen width is bigger than 400px. If I need the smallest value of clamp() to be considered below 400px then I do nothing to the sign of the condition (I keep it positive) but if I need the smallest value to be considered above 400px I need to invert the sign of the condition. That’s why you see (100vw - 400px)*-1000 with the top property.

OK, I get it. This isn’t the more straightforward concept, so let’s do the opposite reasoning and trace our steps to get a better idea of what we’re doing.

For top, we have clamp(10%,(100vw - 400px)*-1000,50%) so…

  • if the screen width (100vw) is smaller than 400px, then the difference (100vw - 400px) is a negative value. We multiply it with another big negative value (-1000 in this case) to get a big positive value that gets clamped to 50%: That means we’re left with top: 50% when the screen size is smaller than 400px.
  • if the screen width (100vw) is bigger than 400px, we end with: top: 10% instead.

The same logic applies to what we’re declaring on the left property. The only difference is that we multiply with 1000 instead of -1000 .

Here’s a secret: You don’t really need all that math. You can experiment until you get it perfect values, but for the sake of the article, I need to explain things in a way that leads to consistent behavior.

It should be noted that a trick like this works with any property that accepts length values (padding, margin, border-width, translate, etc.). We are not limited to changing the position, but other properties as well.

Demos!

Most of you are probably wondering if any of these concepts are at all practical to use in a real-world use case. Let me show you a few examples that will (hopefully) convince you that they are.

Progress bar

The background color changing trick makes for a great progress bar or any similar element where we need to show a different color based on progression.

This demo supports Chrome, Edge, and Firefox at the time of writing.

That demo is a pretty simple example where I define three ranges:

  • Red: [0% 30%]
  • Orange: [30% 60%]
  • Green: [60% 100%]

There’s no wild CSS or JavaScript to update the color. A “magic” background property allows us to have a dynamic color that changes based on computed values.

Editable content

It’s common to give users a way to edit content. We can update color based on what’s entered.

In the following example, we get a yellow “warning” when entering more than three lines of text, and a red “warning” if we go above six lines. This can a way to reduce JavaScript that needs to detect the height, then add/remove a particular class.

This demo supports Chrome, Edge, and Firefox at the time of writing.

Timeline layout

Timelines are great patterns for visualizing key moments in time. This implementation uses three tricks to get one without any media queries. One trick is updating the number of columns, another is hiding some elements on small screens, and the last one is updating the background color. Again, no media queries!

When the screen width is below 600px, all of the pseudo elements are removed, changing the layout from two columns to one column. Then the color updates from a blue/green/green/blue pattern to a blue/green/blue/green one.

Responsive card

Here’s a responsive card approach where CSS properties update based on the viewport size. Normally, we might expect the layout to transition from two columns on large screens to one column on small screens, where the card image is stacked either above or below the content. In this example, however, we change the position, width, height, padding, and border radius of the image to get a totally different layout where the image sits beside the card title.

Speech bubbles

Need some nice-looking testimonials for your product or service? These responsive speech bubbles work just about anywhere, even without media queries.

Fixed button

You know those buttons that are sometimes fixed to the left or right edge of the screen, usually for used to link up a contact for or survey? We can have one of those on large screens, then transform it into a persistent circular button fixed to the bottom-right corner on small screens for more convenient taps.

Fixed alert

One more demo, this time for something that could work for those GDPR cookie notices:

Conclusion

Media queries have been a core ingredient for responsive designs since the term responsive design was coined years ago. While they certainly aren’t going anywhere, we covered a bunch of newer CSS features and concepts that allow us to rely less often on media queries for creating responsive layouts.

We looked at flexbox and grid, clamp(), relative units, and combined them together to do all kinds of things, from changing the background of an element based on its container width, moving positions at certain screen sizes, and even mimicking as-of-yet-unreleased container queries. Exciting stuff! And all without one @media in the CSS.

The goal here is not to get rid or replace media queries but it’s more to optimize and reduce the amount of code especially that CSS has evolved a lot and now we have some powerful tool to create conditional styles. In other words, it’s awesome to see the CSS feature set grow in ways that make our lives as front-enders easier while granting us superpowers to control how our designs behave like we have never had before.


Responsive Layouts, Fewer Media Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/responsive-layouts-fewer-media-queries/feed/ 20 357080
Proposal for CSS @when https://css-tricks.com/proposal-for-css-when/ https://css-tricks.com/proposal-for-css-when/#comments Tue, 21 Sep 2021 22:54:51 +0000 https://css-tricks.com/?p=352328 CSS is on a tear lately. Again, I’ve heard of a brand new thing I’ve never seen before, and again it’s via Miriam: CSS Conditionals.


Proposal for CSS @when originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS is on a tear lately. Again, I’ve heard of a brand new thing I’ve never seen before, and again it’s via Miriam: CSS Conditionals.

There is already such a thing as logic in media queries. In fact, a media query is already logic.

@media (min-width: 600px) {
  /* WHEN this media query is true, do these styles. */
}

And if you want to have styles that are mutually exclusive to the above, you’d write two media queries:

@media (min-width: 600px) {
  /* ... */ 
}
@media (max-width: 599px) {
  /* ... */
}

That’s a little… fidgety. The syntax is much cleaner in this new proposal:

@when media(min-width: 600px) {
  /* ... */ 
}
@else {
  /* ... */ 
}

Looks like you can do multiple conditions via and, have a waterfall logic stack with multiple @else statements, and not just use @media, but @supports as well.

@when media(width >= 400px) and media(pointer: fine) and supports(display: flex) {
  /* A */
} @else supports(caret-color: pink) and supports(background: double-rainbow()) {
  /* B */
} @else {
  /* C */
}

Looks very logical and handy to me!

I saw one little squabble about the naming. @if could be a logical name here too. But Sass uses @if and it would be super annoying to a ton of developers if they had to refactor all their Sass logic to something new or however that would fall out. Should CSS cede to any preprocessor out there? Nah, but Sass has been around a long time and is super popular, and there is a perfectly good alternative, so why cause the pain? In that thread, it’s not just about Sass either — some folks think @when is a better name anyway.


Proposal for CSS @when originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/proposal-for-css-when/feed/ 12 352328
Can we have custom media queries, please? https://css-tricks.com/can-we-have-custom-media-queries-please/ Tue, 07 Sep 2021 22:17:55 +0000 https://css-tricks.com/?p=351276 Stefan Judis notes that there is a spec for custom media queries, but seemingly no momentum on it at the moment. That lack of movement is unfortunate, as it’s almost guaranteed front-end developers all over would start using it as …


Can we have custom media queries, please? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Stefan Judis notes that there is a spec for custom media queries, but seemingly no momentum on it at the moment. That lack of movement is unfortunate, as it’s almost guaranteed front-end developers all over would start using it as soon as it’s ready. I know I would, as I liberally use custom properties now, and the DRYness of custom properties is one of the exact same benefits we’d get with custom media queries.

The syntax is:

@custom-media --narrow-window (max-width: 30em);

@media (--narrow-window) {
  /* narrow window styles */
}
@media (--narrow-window) and (script) {
  /* special styles for when script is allowed */
}

..which I lifted from the Chrome Bug (star it to signal interest).

I’ve come around on the idea of postcss-preset-env, which can polyfill this among many other things. I used to think it was too theoretical which made me nervous — so much CSS isn’t particularly polyfill-able without JavaScript. Plus, if anything changed with the specs, you’ve basically locked yourself into a CSS processor rather than getting future features, as that processing eventually stops processing. But all the CSS transformations in here generally seem pretty chill, deliver a lot of value (like custom media queries!), and you can pick which stage you feel most comfortable with.

To Shared LinkPermalink on CSS-Tricks


Can we have custom media queries, please? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
351276
Detecting Media Query Support in CSS and JavaScript https://css-tricks.com/detecting-media-query-support-in-css-and-javascript/ https://css-tricks.com/detecting-media-query-support-in-css-and-javascript/#respond Mon, 23 Aug 2021 17:16:38 +0000 https://css-tricks.com/?p=350264 You can’t just do @media (prefers-reduced-data: no-preference) alone because, as Kilian Valkhof says:

[…] that would be false if either there was no support (since the browser wouldn’t understand the media query) or if it was supported but the


Detecting Media Query Support in CSS and JavaScript originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
You can’t just do @media (prefers-reduced-data: no-preference) alone because, as Kilian Valkhof says:

[…] that would be false if either there was no support (since the browser wouldn’t understand the media query) or if it was supported but the user wanted to preserve data.

Usually @supports is the tool for this in CSS, but that doesn’t work with @media queries. Turns out there is a solution though:

@media not all and (prefers-reduced-data), (prefers-reduced-data) {
  /* ... */
}

This is a somewhat complex logic puzzle involving media query syntax and how browsers evaluate these things. It’s nice you can ultimately handle a no-support fallback situation in one expression.

To Shared LinkPermalink on CSS-Tricks


Detecting Media Query Support in CSS and JavaScript originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/detecting-media-query-support-in-css-and-javascript/feed/ 0 350264
Are we in a new era of web design? What do we call it? https://css-tricks.com/are-we-in-a-new-era-of-web-design-what-do-we-call-it/ https://css-tricks.com/are-we-in-a-new-era-of-web-design-what-do-we-call-it/#comments Mon, 21 Jun 2021 21:22:33 +0000 https://css-tricks.com/?p=342887 Una is calling it the new responsive. A nod to the era we were most certainly in, the era of responsive design. Where responsive design was fluid grids, flexible media, and media queries, the new responsive is those things …


Are we in a new era of web design? What do we call it? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Una is calling it the new responsive. A nod to the era we were most certainly in, the era of responsive design. Where responsive design was fluid grids, flexible media, and media queries, the new responsive is those things too, but slotted into a wider scope: user preference queries, viewport and form factor, macro layouts, and container styles.

I like the thinking and grouping here and I kinda like the name. It alludes to an evolution and extension of responsive web design rather than a rejection and replacement.

This isn’t the first crack at identifying and naming a shift between eras. Back in 2018, Jen Simmons was doing a talked called “Everything You Know About Web Design Just Changed” where she identified that responsive design was a major shift in how we did layout on the web. And yet, it was firmly defined in an era where layout tools like flexbox and grid didn’t even exist. Now, they do exist, and with them a bevy of other new features that bring more capable graphic design to the web. She called it Intrinsic Design.

I almost like Intrinsic Design more now than I did in 2018, because now, if we attempt to lump in @container queries, the name makes more intuitive sense. We (hopefully will soon) make styling choices based on the intrinsic size of elements. We make styling choices based on the intrinsic nature of the individual users we serve. We make styling choices off the intrinsic qualities of the browser.

I wouldn’t say either of the terms have really caught on though. It’s hard to make a name stick. That little burst of ideating around CSS4 sure didn’t go anywhere.

To Shared LinkPermalink on CSS-Tricks


Are we in a new era of web design? What do we call it? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/are-we-in-a-new-era-of-web-design-what-do-we-call-it/feed/ 7 342887
Platform News: Defaulting to Logical CSS, Fugu APIs, Custom Media Queries, and WordPress vs. Italics https://css-tricks.com/platform-news-defaulting-to-logical-css-fugu-apis-custom-media-queries-and-wordpress-vs-italics/ https://css-tricks.com/platform-news-defaulting-to-logical-css-fugu-apis-custom-media-queries-and-wordpress-vs-italics/#comments Fri, 12 Mar 2021 15:51:32 +0000 https://css-tricks.com/?p=336380 Looks like 2021 is the time to start using CSS Logical Properties! Plus, Chrome recently shipped a few APIs that have raised eyebrows, SVG allows us to disable its aspect ratio, WordPress focuses on the accessibility of its typography, and …


Platform News: Defaulting to Logical CSS, Fugu APIs, Custom Media Queries, and WordPress vs. Italics originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Looks like 2021 is the time to start using CSS Logical Properties! Plus, Chrome recently shipped a few APIs that have raised eyebrows, SVG allows us to disable its aspect ratio, WordPress focuses on the accessibility of its typography, and there’s still no update (or progress) on the development of CSS custom media queries.

Let’s jump right into the news…

Logical CSS could soon become the new default

Six years after Mozilla shipped the first bits of CSS Logical Properties in Firefox, this feature is now on a path to full browser support in 2021. The categories of logical properties and values listed in the table below are already supported in Firefox, Chrome, and the latest Safari Preview.

CSS property or valueThe logical equivalent
margin-topmargin-block-start
text-align: righttext-align: end
bottominset-block-end
border-leftborder-inline-start
(n/a)margin-inline

Logical CSS also introduces a few useful shorthands for tasks that in the past required multiple declarations. For example, margin-inline sets the margin-left and margin-right properties, while inset sets the top, right, bottom and left properties.

/* BEFORE */
main {
  margin-left: auto;
  margin-right: auto;
}

/* AFTER */
main {
  margin-inline: auto;
}

A website can add support for an RTL (right-to-left) layout by replacing all instances of left and right with their logical counterparts in the site’s CSS code. Switching to logical CSS makes sense for all websites because the user may translate the site to a language that is written right-to-left using a machine translation service. The biggest languages with RTL scripts are Arabic (310 million native speakers), Persian (70 million), and Urdu (70 million).

/* Switch to RTL when Google translates the page to an RTL language */
.translated-rtl {
  direction: rtl;
}

David Bushell’s personal website now uses logical CSS and relies on Google’s translated-rtl class to toggle the site’s inline base direction. Try translating David’s website to an RTL language in Chrome and compare the RTL layout with the site’s default LTR layout.

Chrome ships three controversial Fugu APIs

Last week Chrome shipped three web APIs for “advanced hardware interactions”: the WebHID and Web Serial APIs on desktop, and Web NFC on Android. All three APIs are part of Google’s capabilities project, also known as Project Fugu, and were developed in W3C community groups (though they’re not web standards).

  • The WebHID API allows web apps to connect to old and uncommon human interface devices that don’t have a compatible device driver for the operating system (e.g., Nintendo’s Wii Remote).
  • The Web Serial API allows web apps to communicate (“byte by byte”) with peripheral devices, such as microcontrollers (e.g., the Arduino DHT11 temperature/humidity sensor) and 3D printers, through an emulated serial connection.
  • Web NFC allows web apps to wirelessly read from and write to NFC tags at short distances (less than 10 cm).

Apple and Mozilla, the developers of the other two major browser engines, are currently opposed to these APIs. Apple has decided to “not yet implement due to fingerprinting, security, and other concerns.” Mozilla’s concerns are summarized on the Mozilla Specification Positions page.

Source: webapicontroversy.com

Stretching SVG with preserveAspectRatio=none

By default, an SVG scales to fit the <svg> element’s content box, while maintaining the aspect ratio defined by the viewBox attribute. In some cases, the author may want to stretch the SVG so that it completely fills the content box on both axes. This can be achieved by setting the preserveAspectRatio attribute to none on the <svg> element.

View demo

Distorting SVG in this manner may seem counterintuitive, but disabling aspect ratio via the preserveAspectRatio=none value can make sense for simple, decorative SVG graphics on a responsive web page:

This value can be useful when you are using a path for a border or to add a little effect on a section (like a diagonal [line]), and you want the path to fill the space.

WordPress tones down the use of italics

An italic font can be used to highlight important words (e.g., the <em> element), titles of creative works (<cite>), technical terms, foreign phrases (<i>), and more. Italics are helpful when used discreetly in this manner, but long sections of italic text are considered an accessibility issue and should be avoided.

Italicized text can be difficult to read for some people with dyslexia or related forms of reading disorders.

Putting the entire help text in italics is not recommended

WordPress 5.7, which was released earlier this week, removed italics on descriptions, help text, labels, error details text, and other places in the WordPress admin to “improve accessibility and readability.”

In related news, WordPress 5.7 also dropped custom web fonts, opting for system fonts instead.

Still no progress on CSS custom media queries

The CSS Media Queries Level 5 module specifies a @custom-media rule for defining custom media queries. This proposed feature was originally added to the CSS spec almost seven years ago (in June 2014) and has since then not been further developed nor received any interest from browser vendors.

@custom-media --narrow-window (max-width: 30em);

@media (--narrow-window) {
  /* narrow window styles */
}

A media query used in multiple places can instead be assigned to a custom media query, which can be used everywhere, and editing the media query requires touching only one line of code.

Custom media queries may not ship in browsers for quite some time, but websites can start using this feature today via the official PostCSS plugin (or PostCSS Preset Env) to reduce code repetition and make media queries more readable.

On a related note, there is also the idea of author-defined environment variables, which (unlike custom properties) could be used in media queries, but this potential feature has not yet been fully fleshed out in the CSS spec.

@media (max-width: env(--narrow-window)) {
  /* narrow window styles */
}

Platform News: Defaulting to Logical CSS, Fugu APIs, Custom Media Queries, and WordPress vs. Italics originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/platform-news-defaulting-to-logical-css-fugu-apis-custom-media-queries-and-wordpress-vs-italics/feed/ 2 336380
Nested Media Queries https://css-tricks.com/nested-media-queries/ https://css-tricks.com/nested-media-queries/#comments Tue, 09 Feb 2021 20:34:45 +0000 https://css-tricks.com/?p=334168 We don’t have “regular” nesting in CSS. Maybe this becomes a thing someday, or something like it. That would be cool, although that pre-spec doesn’t mention anything about media queries. I’d hope we get that right out of the gate …


Nested Media Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
We don’t have “regular” nesting in CSS. Maybe this becomes a thing someday, or something like it. That would be cool, although that pre-spec doesn’t mention anything about media queries. I’d hope we get that right out of the gate if we ever do get native CSS nesting. In fact, I’d trade it for selector nesting in a heartbeat. I’d say that’s the most useful thing a CSS preprocessor like Sass does today.

But I’ve digressed before I even began. You can nest media queries in native CSS, as long as you’re doing it from the root. It’s funny to see in native CSS, but it works!

@media screen {
  @media (min-width: 1px) {
    @media (min-height: 1px) {
      @media (max-width: 9999px) {
        @media (max-height: 9999px) {
          body {
            background: red;
          }
        }
      }
    }
  }
}

To Shared LinkPermalink on CSS-Tricks


Nested Media Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/nested-media-queries/feed/ 2 334168
Creating websites with prefers-reduced-data https://css-tricks.com/creating-websites-with-prefers-reduced-data/ https://css-tricks.com/creating-websites-with-prefers-reduced-data/#comments Tue, 08 Dec 2020 15:45:37 +0000 https://css-tricks.com/?p=330326 Spoiler alert: There is no support for it yet. But it is defined in the Media Queries Level 5 spec that includes other recent, but more familiar user preference features, like prefers-color-scheme and prefers-reduced-motion.

The Polypane blog goes into …


Creating websites with prefers-reduced-data originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Spoiler alert: There is no support for it yet. But it is defined in the Media Queries Level 5 spec that includes other recent, but more familiar user preference features, like prefers-color-scheme and prefers-reduced-motion.

The Polypane blog goes into incredible depth on prefers-reduced-data, especially for something that we have yet to see in the wild. That’s actually what makes the Polypane team an ideal voice on the subject. It’s product is a browser that is able to emulate the feature behind a Chromium flag.

At the same time, it’s worth noting that the spec calls out two significant potential issues with this feature in its current state:

  • It may be an undesired source of fingerprinting, with a bias towards low income with limited data. 
  • This feature is an early draft, and the CSS-WG does not consider it ready for shipping in production.

But that’s not to say we can’t start getting acquainted with it. Here’s how it works:

@media (prefers-reduced-data: reduce) {
  /* Stuff for reduced data preferences */
}

@media (prefers-reduced-data: no-preference) {
  /* Stuff for no data preferences */
}

What I appreciate from this post is the wealth of possible use cases it lists. Allow me to summarize:

  • Conditionally load fonts. As in, make a @font-face declaration then call the font on the body, once for users with no-preference to get the custom font, and again for users with reduced to get a lighter stack.
  • Background images. Have you ever used a giant splash image as the background for some full-width hero component? Maybe that can be served just to folks who have no-preference while those with reduced get either a smaller variation or no image at all.
  • Smaller images in HTML. This is clever because remember that we have the media attribute on the <source> element. So, we can instruct browsers to use certain images when working with <picture>, a la: <source srcset="small.jpg" media="(prefers-reduced-data: reduce)" />.
  • Conditionally preload and autoplay videos. Just as we can work with this feature in HTML, we can use it in JavaScript, too, by using window.matchMedia('(prefers-reduced-data: no-preference)').matches to set autoplay and preload attributes on a video based on data preferences.
  • Ditch infinite scrolling. I’d generally ditch this pattern in the first place, but we can certainly disable it so users who prefer reduced data aren’t force-fed more content (and thus, more data) just by reaching the end of a page.

That’s not a definitive list of ideas, of course! We talk all the time about serving the right assets to the right people at the right time and this media feature is a great tool to help us do that in certain contexts. Think also of:

  • Providing low-res versions of downloaded assets (e.g. PDFs)
  • Linking to certain sites or pages that have heavy experiences
  • Conditionally loading entire scripts, stylesheets, or libraries based on preference
  • Probably a gazillion other and more clever things…

And this advice is golden:

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

Yep. The same sort of idea as “mobile-first” responsive design: start by designing for reduced data and then enhance as we scale up.

To Shared LinkPermalink on CSS-Tricks


Creating websites with prefers-reduced-data originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/creating-websites-with-prefers-reduced-data/feed/ 2 330326
A Complete Guide to CSS Media Queries https://css-tricks.com/a-complete-guide-to-css-media-queries/ https://css-tricks.com/a-complete-guide-to-css-media-queries/#comments Fri, 02 Oct 2020 15:13:37 +0000 https://css-tricks.com/?p=319964 Media queries can modify the appearance (and even behavior) or a website or app based on a matched set of conditions about the user's device, browser or system settings.


A Complete Guide to CSS Media Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS 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.

HTML

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>
CSS

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;
JavaScript

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 )' )


// Note the `matches` property
if ( mediaQuery.matches ) {
  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.matches) {
    // 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.

Syntax for CSS media queries.
@media
@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

FeatureSummaryValuesAdded
widthDefines the widths of the viewport. This can be a specific number (e.g. 400px) or a range (using min-width and max-width).<length>
heightDefines 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-ratioDefines the width-to-height aspect ratio of the viewport<ratio>
orientationThe way the screen is oriented, such as tall (portrait) or wide (landscape) based on how the device is rotated.portrait

landscape
overflow-blockChecks 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

optional-paged

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

none
Media Queries Level 4

Display Quality

FeatureSummaryValuesAdded
resolutionDefines the target pixel density of the device<resolution>

infinite
scanDefines 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

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

fast

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

additive

subtractive
display-modeTests 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

standalone

minimal-ui

browser
Web App Manifest

Color

FeatureSummaryValuesAdded
colorDefines 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-indexDefines 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>
monochromeThe number of bits per pixel that a device’s monochrome supports, where zero is no monochrome support.<integer>
color-gamutDefines the range of colors supported by the browser and device, which could be srgb, p3 or rec2020srgb

p3

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

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

none
Media Queries Level 5

Interaction

FeatureSummaryValuesAdded
pointerSort 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

fine

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

none
Media Queries Level 4
any-pointerChecks 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

fine

none
Media Queries Level 4
any-hoverChecks 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

none
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.

FeatureSummaryValuesAdded
video-color-gamutDescribes the approximate range of colors supported by the video plane of the browser and user devicesrgb

p3

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

high
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>

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

Scripting

FeatureSummaryValuesAdded
scriptingChecks whether the device allows scripting (i.e. JavaScript) where enabled allows scripting, iniital-only enabled

initial-only

Media Queries Level 5

User Preference

FeatureSummaryValuesAdded
prefers-reduced-motionDetects if the user’s system settings are set to reduce motion on the page, which is a great accessibility check.no-preference

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

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

high

low

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

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

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

reduce
Media Queries Level 5

Deprecated

NameSummaryRemoved
device-aspect-ratioThe width-to-height aspect ratio of the output deviceMedia Queries Level 4
device-heightThe height of the device’s surface that displays rendered elementsMedia Queries Level 4
device-widthThe width of the device’s surface that displays rendered elementsMedia Queries Level 4
Operators

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.

and

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! */
  }
}

not

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;
  }
}

Want to go deeper? Check out “CSS Media Queries: Quick Reference & Guide” from the DigitalOcean community for more examples that follow the syntax for media quieries.


Do you really need CSS media queries?

Media queries are 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.” 

In “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.


Matching 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. So, that last example can be converted to the new syntax, like so:

@media (30em <= width <= 80em) {
  /* ... */
}

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.”


Accessibility

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

prefers-reduced-motion

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. 

prefers-contrast

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.

inverted-colors

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
MacOS accessibility preferences.

The problem with inverted 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.

prefers-color-scheme

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: dark) {
  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 CSS 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 container queries try to solve. Ideally we could adapt styles of an element according to the size of itself instead of of the size of the viewport. Chrome 105 released support for CSS Container Queries. Same deal with Safari 16.1. Firefox is all we’re really waiting at the time of writing to get broad support.

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

Desktop

ChromeFirefoxIEEdgeSafari
106109No10616.0

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
108No10816.0

Examples

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.


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.

Specifications


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


A Complete Guide to CSS Media Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/a-complete-guide-to-css-media-queries/feed/ 6 319964
Interaction Media Features and Their Potential (for Incorrect Assumptions) https://css-tricks.com/interaction-media-features-and-their-potential-for-incorrect-assumptions/ https://css-tricks.com/interaction-media-features-and-their-potential-for-incorrect-assumptions/#comments Mon, 14 Sep 2020 14:43:56 +0000 https://css-tricks.com/?p=320427 The Media Queries Level 4 Interaction Media Features — pointer, 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 device.


Interaction Media Features and Their Potential (for Incorrect Assumptions) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
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 to 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.

FeatureTouchscreenTouchscreen + MouseDesktop/LaptopDesktop/Laptop + Touchscreen
pointer:coarsetruetruefalsefalse
pointer:finefalsefalsetruetrue
hover:nonetruetruefalsefalse
hover:hoverfalsefalsetruetrue

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:

FeatureTouchscreenTouchscreen + MouseDesktop/LaptopDesktop/Laptop + Touchscreen
any-pointer:coarsetruetruefalsetrue
any-pointer:finefalsetruetruetrue
any-hover:nonetruefalsefalsefalse
any-hover:hoverfalsetruetruetrue
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 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.

? Don’t

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.

? Do

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.


Interaction Media Features and Their Potential (for Incorrect Assumptions) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/interaction-media-features-and-their-potential-for-incorrect-assumptions/feed/ 3 320427
Working with JavaScript Media Queries https://css-tricks.com/working-with-javascript-media-queries/ https://css-tricks.com/working-with-javascript-media-queries/#comments Mon, 07 Sep 2020 15:37:33 +0000 https://css-tricks.com/?p=319966 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 …


Working with JavaScript Media Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
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.

Desktop

ChromeFirefoxIEEdgeSafari
9610125.1

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
10810735.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.

Conclusion

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.


Working with JavaScript Media Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/working-with-javascript-media-queries/feed/ 21 319966
Responsive Styling Using Attribute Selectors https://css-tricks.com/responsive-styling-using-attribute-selectors/ https://css-tricks.com/responsive-styling-using-attribute-selectors/#comments Tue, 30 Jun 2020 14:35:04 +0000 https://css-tricks.com/?p=313139 One of the challenges we face when implementing class-based atomic styling is that it often depends on a specific breakpoint for context. We can prefix each breakpoint. This works well until we start adding multiple classes. That’s when it becomes difficult to keep a track what relates to what and where to add, remove. or change stuff.


Responsive Styling Using Attribute Selectors originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
One of the challenges we face when implementing class-based atomic styling is that it often depends on a specific breakpoint for context.

<div class="span-12"></div> <!-- we want this for small screens  -->
<div class="span-6"></div>  <!-- we want this for medium screens -->
<div class="span-4"></div>  <!-- we want this for large screens  -->

It’s common to use a prefix to target each breakpoint:

<div class="sm-span-12 md-span-6 lg-span-4"></div>

This works well until we start adding multiple classes. That’s when it becomes difficult to keep a track what relates to what and where to add, remove. or change stuff.

<div class="
  sm-span-12 
  md-span-6 
  lg-span-4 
  sm-font-size-xl 
  md-font-size-xl 
  lg-font-size-xl 
  md-font-weight-500 
  lg-font-weight-700">
</div>

We can try to make it more readable by re-grouping:

<div class="
  sm-span-12 
  sm-font-size-xl 


  md-span-6 
  md-font-size-xl 
  md-font-weight-500 


  lg-span-4 
  lg-font-size-xl 
  lg-font-weight-700">
</div>

We can add funky separators (invalid class names will be ignored):

<div class="
  [
   sm-span-12 
   sm-font-size-xl 
  ],[
   md-span-6 
   md-font-size-xl 
   md-font-weight-500 
  ],[
   lg-span-4 
   lg-font-size-xl 
   lg-font-weight-700
  ]">
</div>

But this still feels messy and hard to grasp, at least to me.

We can get a better overview and avoid implementation prefixes by grouping attribute selectors instead of actual classes:

<div 
  data-sm="span-12 font-size-lg"
  data-md="span-6 font-size-xl font-weight-500"
  data-lg="span-4 font-size-xl font-weight-700"
>
</div>

These aren’t lost of classes but a whitespace-separated list of attributes we can select using [attribute~="value"], where ~= requires the exact word to be found in the attribute value in order to match.

@media (min-width: 0) {
 [data-sm~="span-1"] { /*...*/ }              
 [data-sm~="span-2"] { /*...*/ }   
 /* etc. */ 
}
@media (min-width: 30rem) {
 [data-md~="span-1"] { /*...*/ }   
 [data-md~="span-2"] { /*...*/ }   
 /* etc. */   
}
@media (min-width: 60rem) {
 [data-lg~="span-1"] { /*...*/ }   
 [data-lg~="span-2"] { /*...*/ }   
 /* etc. */   
}

It may be a bit odd-looking but I think translating atomic classes to  attributes is fairly straightforward (e.g. .sm-span-1 becomes [data-sm~="span-1"]). Plus, attribute selectors have the same specificity as classes, so we lose nothing there. And, unlike classes, attributes can be written without escaping special characters, like /+.:?.

That’s all! Again, this is merely an idea that aims to make switching declarations in media queries easier to write, read and manage. It’s definitely not a proposal to do away with classes or anything like that.


Responsive Styling Using Attribute Selectors originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/responsive-styling-using-attribute-selectors/feed/ 13 313139
SVG, Favicons, and All the Fun Things We Can Do With Them https://css-tricks.com/svg-favicons-and-all-the-fun-things-we-can-do-with-them/ https://css-tricks.com/svg-favicons-and-all-the-fun-things-we-can-do-with-them/#comments Fri, 24 Apr 2020 14:58:29 +0000 https://css-tricks.com/?p=306644 Favicons are the little icons you see in your browser tab. They help you understand which site is which when you’re scanning through your browser’s bookmarks and open tabs. They’re a neat part of internet history that are capable of …


SVG, Favicons, and All the Fun Things We Can Do With Them originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Favicons are the little icons you see in your browser tab. They help you understand which site is which when you’re scanning through your browser’s bookmarks and open tabs. They’re a neat part of internet history that are capable of performing some cool tricks.

One very new trick is the ability to use SVG as a favicon. It’s something that most modern browsers support, with more support on the way.

Here’s the code for how to add favicons to your site. Place this in your website’s <head>:

<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="manifest" href="/manifest.webmanifest">

And place this code in your site’s web app manifest:

{
  "icons": [
    { "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" },
    { "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" }
  ]
}

Browsers that do support SVG favicons will override the first link element declaration and honor the second instead. Browsers that do not support SVG favicons but do support web app manifests will use the higher-resolution images. All other browsers fall back to using the favicon.ico file. This ensures that all browsers that support favicons can enjoy the experience. 

You may also notice the alternate attribute value for our rel declaration in the second line. This programmatically communicates to the browser that the favicon with a file format that uses .ico is specifically used as an alternate presentation.

Following the favicons is a line of code that loads another SVG image, one called safari-pinned-tab.svg. This is to support Safari’s pinned tab functionality, which existed before other browsers had SVG favicon support. There’s additional files you can add here to enhance your site for different apps and services, but more on that in a bit.

Here’s more detail on the current level of SVG favicon support:

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

Desktop

ChromeFirefoxIEEdgeSafari
8041No80No

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
108NoNoNo

Why SVG?

You may be questioning why this is needed. The .ico file format has been around forever and can support images up to 256×256 pixels in size. Here are three answers for you.

Ease of authoring

It’s a pain to make .ico files. The file is a proprietary format used by Microsoft, meaning you’ll need specialized tools to make them. SVG is an open standard, meaning you can use them without any further tooling or platform lock-in.

Future-proofing

Retina? 5k? 6k? When we use a resolution-agnostic SVG file for a favicon, we guarantee that our favicons look crisp on future devices, regardless of how large their displays get

Performance

SVGs are usually very small files, especially when compared to their raster image counterparts — even more-so if you optimize them beforehand. By only using a 16×16 pixel favicon as a fallback for browsers that don’t support SVG, we provide a combination that enjoys a high degree of support with a smaller file size to boot. 

This might seem a bit extreme, but when it comes to web performance, every byte counts!

Tricks

Another cool thing about SVG is we can embed CSS directly in it. This means we can do fun things like dynamically adjust them with JavaScript, provided the SVG is declared inline and not embedded using an img element.

<svg  version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
  <style>
    path { fill: #272019; }
  </style>
  <!-- etc. -->
</svg>

Since SVG favicons are embedded using the link element, they can’t really be modified using JavaScript. We can, however, use things like emoji and media queries.

Emoji

Lea Verou had a genius idea about using emoji inside of SVG’s text element to make a quick favicon with a transparent background that holds up at small sizes.

https://twitter.com/LeaVerou/status/1241619866475474946

In response, Chris Coyier whipped up a neat little demo that lets you play around with the concept.

Dark Mode support

Both Thomas Steiner and Mathias Bynens independently stumbled across the idea that you can use the prefers-color-scheme media query to provide support for dark mode. This work is built off of Jake Archibald’s exploration of SVG and media queries.

<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
  <style>
    path { fill: #000000; }
    @media (prefers-color-scheme: dark) {
      path { fill: #ffffff; }
    }
  </style>
  <path d="M111.904 52.937a1.95 1.95 0 00-1.555-1.314l-30.835-4.502-13.786-28.136c-.653-1.313-2.803-1.313-3.456 0L48.486 47.121l-30.835 4.502a1.95 1.95 0 00-1.555 1.314 1.952 1.952 0 00.48 1.99l22.33 21.894-5.28 30.918c-.115.715.173 1.45.768 1.894a1.904 1.904 0 002.016.135L64 95.178l27.59 14.59c.269.155.576.232.883.232a1.98 1.98 0 001.133-.367 1.974 1.974 0 00.768-1.894l-5.28-30.918 22.33-21.893c.518-.522.71-1.276.48-1.99z" fill-rule="nonzero"/>
</svg>

For supporting browsers, this code means our star-shaped SVG favicon will change its fill color from black to white when dark mode is activated. Pretty neat!

Other media queries

Dark mode support got me thinking: if SVGs can support prefers-color-scheme, what other things can we do with them? While the support for Level 5 Media Queries may not be there yet, here’s some ideas to consider:

Mockup of four SVG favicon treatments. The first treatment is a pink star with a tab title of, “SVG Favicon.” The second treatment is a hot pink star with a tab title of, “Light Level SVG Favicon.” The third treatment is a light pink star with a tab title of, “Inverted Colors SVG Favicon.” The fourth treatment is a black pink star with a tab title of, “High Contrast Mode SVG Favicon.” The tabs are screen captures from Microsoft Edge, with the browser chrome updating to match each specialized mode.
A mockup of how these media query-based adjustments could work.

Keep it crisp

Another important aspect of good favicon design is making sure they look good in the small browser tab area. The secret to this is making the paths of the vector image line up to the pixel grid, the guide a computer uses to turn SVG math into the bitmap we see on a screen. 

Here’s a simplified example using a square shape:

A crisp orange square on a white background. There is also a faint grid of gray horizontal and vertical lines that represent the pixel grid. Screenshot from Figma.

When the vector points of the square align to the pixel grid of the artboard, the antialiasing effect a computer uses to smooth out the shapes isn’t needed. When the vector points aren’t aligned, we get a “smearing” effect:

A blurred orange square on a white background. There is also a faint grid of gray horizontal and vertical lines that represent the pixel grid. Screenshot from Figma.

A vector point’s position can be adjusted on the pixel grid by using a vector editing program such as Figma, Sketch, Inkscape, or Illustrator. These programs export SVGs as well. To adjust a vector point’s location, select each node with a precision selection tool and drag it into position.

Some more complicated icons may need to be simplified, in order to look good at such a small size. If you’re looking for a good primer on this, Jeremy Frank wrote a really good two-part article over at Vidget.

Go the extra mile

In addition to favicons, there are a bunch of different (and unfortunately proprietary) ways to use icons to enhance its experience. These include things like the aforementioned pinned tab icon for Safari¹, chat app unfurls, a pinned Windows start menu tile, social media previews, and homescreen launchers.

If you’re looking for a great place to get started with these kinds of enhancements, I really like realfavicongenerator.net.

Icon output from realfavicongenerator.net arranged in a grid using CSS-Trick’s logo. There are two rows of five icons: android-chrome-192x192.png, android-chrome-384x384.png, apple-touch-icon.png, favicon-16x16.png, favicon-32x32.png, mstile-150x150.png, safari-pinned-tab.svg, favicon.ico, browserconfig.xml, and site.webmanifest.
It’s a lot, but it guarantees robust support.

A funny thing about the history of the favicon: Internet Explorer was the first browser to support them and they were snuck in at the 11th hour by a developer named Bharat Shyam:

As the story goes, late one night, Shyam was working on his new favicon feature. He called over junior project manager Ray Sun to take a look.

Shyam commented, “This is good, right? Check it in?”, requesting permission to check the code into the Internet Explorer codebase so it could be released in the next version. Sun didn’t think too much of it, the feature was cool and would clearly give IE an edge. So he told Shyam to go ahead and add it. And just like that, the favicon made its way into Internet Explorer 5, which would go on to become one of the largest browser releases the web has ever seen.

The next day, Sun was reprimanded by his manager for letting the feature get by so quickly. As it turns out, Shyam had specifically waited until later in the day, knowing that a less experienced Program Manager would give him a pass. But by then, the code had been merged in. Incidentally, you’d be surprised just how many relatively major browser features have snuck their way into releases like this.

From How We Got the Favicon by Jay Hoffmann

I’m happy to see the platform throw a little love at favicons. They’ve long been one of my favorite little design details, and I’m excited that they’re becoming more reactive to user’s needs. If you have a moment, why not sneak a SVG favicon into your project the same way Bharat Shyam did way back in 1999. 


¹ I haven’t been able to determine if Safari is going to implement SVG favicon support, but I hope they do. Has anyone heard anything?


SVG, Favicons, and All the Fun Things We Can Do With Them originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/svg-favicons-and-all-the-fun-things-we-can-do-with-them/feed/ 9 306644
Solving Sticky Hover States with @media (hover: hover) https://css-tricks.com/solving-sticky-hover-states-with-media-hover-hover/ https://css-tricks.com/solving-sticky-hover-states-with-media-hover-hover/#comments Tue, 18 Feb 2020 20:38:41 +0000 https://css-tricks.com/?p=303871 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.


Solving Sticky Hover States with @media (hover: hover) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
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.


To Shared LinkPermalink on CSS-Tricks


Solving Sticky Hover States with @media (hover: hover) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/solving-sticky-hover-states-with-media-hover-hover/feed/ 8 303871