Thoughts on Media Queries for Elements

written by jonathantneal on February 7, 2013 in CSS with 18 comments

This post was inspired by my friend and co-creator of normalize.css, Nicolas Gallagher.

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

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

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’ Tweet, Chris Coyier on CSS Specificity, MediaClass (polyfills element media queries), and a great conversation with Ian Hickson, who taught me the difference between : and ::.