Thoughts on Media Queries for Elements

Avatar of Chris Coyier
Chris Coyier on (Updated on )

Imagine something like these Transformer Tabs as a widget in a fluid column in a responsive design. Depending on the browser window width, perhaps this design is either 4, 2, or 1 column wide. When it breaks from 4 to 2, the column probably temporarily gets wider than it was, even though the screen is narrower. It would be preferable when writing the media query logic for those tabs to consider how much space the widget has available rather than the entire window, which might be totally unrelated, especially when re-using this widget.

Jonathan Neal has some thoughts on how this might work, including the complicated bits you might not have thought about, like how a widgets contents might affect its parent container and cause an infinite loop.

Jonathan’s site is offline now, so I’m going to copy the contents of this blog post here for posterity.

This post was inspired by my friend and co-creator of normalize.cssNicolas Gallagher.

We need native CSS media queries at the element/component/widget level, not just the viewport. Make it so, internetz.— @necolas

So, without any fanfare, I want to share my thoughts on element media queries, and then open it up for discussion in the comments.

Thought #1: What the Markup Would Be

We would use a pseudo class, because we are targeting the state of an element. Some examples of pseudo classes are :hover:focus, and :checked.

We would not use a pseudo element, since we are not targeting a shadow element within the element. Some examples of pseudo elements are ::first-letter::before, and ::after. Don’t be fooled by IE7&8, :before is not the correct syntax. In fact, if you are not supporting IE7&8, you should start using the correct syntax to free yourself of this legacy inconsistency.

[The] :: notation is introduced by the current document in order to establish a discrimination between pseudo-classes and pseudo-elements. For compatibility with existing style sheets, user agents must also accept the previous one-colon notation for pseudo-elements introduced in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and :after).— Selectors W3C Working Draft

We would name the pseudo class media after its predecessor, using parenthesis to wrap the queries, similar to :not and :contains.

.widget:media(max-width: 30em) {color: tomato;}

Multiple queries would require multiple parentheses.

.widget:media((max-width: 30em) and (min-width: 30em)) {color: bisque;}

Thought #2: How Ems Would Work

The size of an em would be relative to the font size of the element, just as it works with existing CSS values. The rem unit would be used to target the font size of the document.

html {font-size: 16px;} 
.parent {font-size: 12px;} 
.parent > *:media(max-width: 30em) {/* applied up to 360px */}  
.parent > *:media(max-width: 30rem) {/* applied up to 480px */}

This brings up an issue with existing media queries. Presently, when we define the font size of html as 12px, does @media (max-width: 30em) evaluate to 360px or 480px? If we believe that @media “lives” on the html element, then the answer is 360px. On the other hand, if we believe that @media “lives” in some ether beyond html, then the answer is 480px. Sadly, most browsers agree with the later interpretation. Therefore, as a side benefit to this element media queries discussion, we should specify that the size of an em in a @media query should be relative to the font size of the html element.

Thought #3: How Infinite Loops Would Be Handled

Infinite loops would freeze at the offending block. While infinite loops are much more likely to happen with element media queries, this issue has been around since :hover. Therefore, a clear specification would be doubly useful.

.widget {color: salmon;width: 100%;} 
.widget:media(max-width: 320px) {color: whitesmoke;width: 321px;} 
/* the infinite loop is stopped, .widget is whitesmoke with a width of 321px */

Similarly, this would address classic CSS looping issues.

.widget {color: plum;} 
.widget:hover {color: orange;display: none;} 
/* the infinite loop is stopped, .widget is not displayed, but is otherwise orange */

Q&A

Should element media queries be able to target the page, like .widget:media(page-max-width: 30em) and .widget:media(device-max-width: 30em)?

Yes, taking advantage of the syntax like this could really improve the readability of stylesheets. I could imagine a lot of developers preferring these kinds of :media pseudo class queries over traditional @media queries. In fact, a lot of developers are already trying to do things like this with nesting in SASS.

Should the number of queries in the :media pseudo class add to the selector weight, so that .widget:media((min-width: 2em) and (max-width: 30em)) wins over .widget:media(max-width: 30em)?

No, because @media queries are not selectors and therefore do not have any weight. In contrast, *:not(#foo) has more weight than *:not(.foo) because the pseudo class is evaluating selectors, and selectors always add weight. On the other hand, @media (min-width: 5em) and (max-width: 500em) does not have more weight than @media (max-width: 500em).

Inspirations

Necolas’ TweetChris Coyier on CSS SpecificityMediaClass (polyfills element media queries), and a great conversation with Ian Hickson, who taught me the difference between : and ::.