What are Element Queries?
Element Queries are styles that apply to an element based on its own properties on the page, instead of the width or height of the browser.
EQCSS.js
If you want to clone the EQCSS repository from github you can:
git clone https://github.com/eqcss/eqcss/archive/gh-pages.zip
Or alternatively if you use NPM, you can add EQCSS to your project with the following command:
npm install eqcss
EQCSS.js
to your HTMLOnce you have downloaded EQCSS, you can add it to your HTML with a <script>
tag like this:
<script src=EQCSS.js></script>
This file (EQCSS.js
) includes support for all current browsers IE9 and up. To add IE8 support, we required the use of a lot of other polyfills. Keep in mind that IE8 doesn't even support CSS @media
queries without a polyfill, so it's pretty amazing that the EQCSS plugin manages to bring these advanced features to IE8 as well! To add IE8 support to a site using EQCSS, add the following line before your link to the main plugin:
<!--[if lt IE 9]><script src=EQCSS-polyfills.js></script><![endif]-->
By default, the EQCSS plugin will compute any styles it finds once the page loads and also whenever it detects the browser resize, similar to @media
queries. The EQCSS.apply()
function can also be called manually on other events, like keyup
or click
, or triggered by JavaScript functions. For example, JavaScript could trigger EQCSS.apply()
to recalculate styles after content within UI elements on the page has been updated, even if nothing else about the page has changed.
The EQCSS.js
plugin can read styles in three ways. You can include EQCSS inside any <style>
tags in an HTML page where EQCSS.js
has been loaded. You are also able to write EQCSS inside external .css
files linked into a page that includes EQCSS.js
. And the last way you're able to write EQCSS styles is inside a custom <script type="text/eqcss">
tag or hosted in an externally .eqcss
stylesheet, linked using a <script>
tag as the document source.
The easiest way to write EQCSS is right in your CSS, just be sure to add a comment at the top of the file reminding other developers that EQCSS is required for that file.
The EQCSS syntax for writing element queries is very similar to the formatting for CSS @media
queries, but instead of @media
we start the query with @element
. The only other piece of information we need to supply EQCSS is at least one element these styles should apply to. Here's how I would open a scope for an element named <div class="widget">
in my HTML:
@element '.widget' {
}
The element selector between the quotes can be any valid CSS selector. With this query we have created a new scope on the .widget
element. We haven't included a responsive condition yet, so these styles will apply to our element at all times.
Using style scoping on an element allows you to use the $parent
meta-selector for example, because JavaScript can now easily find the parentNode
of the elements you have scoped. CSS includes a direct descendant selector, >
, that allows you to select only elements that are the children of a specified element. But CSS offers no way to travel the other way, to select the element containing something, its parent.
Now that we have opened a scope for the .widget
element, this is how we could write a style targeting its parent element:
@element '.widget' {
$parent {
/* These styles apply to the parent of .widget */
}
}
Other special meta-selectors
available inside any @element
query are $prev
and $next
selectors, which represent to previous and next sibling elements. CSS can reach the element after a specified element with + *
, as in .widget + *
, but as with $parent
CSS has no way of moving backward through the document to find the element before a specified element.
@element '.widget' {
$prev {
/* These styles apply to the element before .widget */
}
$next {
/* These styles apply to the element after .widget */
}
}
CSS @media
queries work in two dimensions, width and height, but the EQCSS syntax supports many more responsive conditions than regular CSS. Instead of just working with the width or height of the browser, you can also write style that apply to elements based on other things as well, like how many child elements an element has, or how many characters of text are inside that element at the moment.
@element '.widget' and (min-width: 500px) {
/* CSS rules here */
}
Here's the formatting for @element
queries with responsive conditions added to the scope.
@element {selector} and {condition} [ and {condition} ]* { {css} }
Or to put that another way, to make a query that turns the body red when the widget element is 500px wide would look something like this:
@element '.widget' and (min-width: 500px) {
body {
background: red;
}
}
The full list of responsive conditions supported in the EQCSS syntax are the following:
min-width
max-width
min-height
max-height
min-characters
max-characters
min-lines
max-lines
min-children
max-children
min-scroll-y
max-scroll-y
min-scroll-x
max-scroll-x
You can combine any number of these conditions in your element queries for truly multi-dimensional responsive styles. This gives you much more control over how your designs show up. For example, if you only wanted to shrink the font size of a header that had more than 15 characters when it's less than 600px wide, you could combine min-characters: 16
and max-width: 600px
like this:
h1 {
font-size: 24pt;
}
@element 'h1' and (min-characters: 16) and (max-width: 600px) {
$this {
font-size: 20pt;
}
}
One of the problems you might run into once you start writing scoped CSS with responsive conditions is that if you have multiple instances of the same selector on a page, any CSS inside an element query referencing that selector will apply those styles to all elements that match that selector when the first element to match that selector meets the responsive conditions. This means that for our .widget
example, if we have one widget in the sidebar, and another full-width in the footer, if we write our element query like this:
@element '.widget' and (min-width: 500px) {
.widget h2 {
font-size: 14pt;
}
}
What will happen is when either .widget
on the page reaches 500px
, the new style will apply to both .widget
elements, which is probably not what we want to happen in most cases. This is where style scoping comes in!
The two parts of the responsive query are the selector and the condition, so if we want to target only those elements on the page that match both the selector and the condition at any given time, we can use the meta-selector $this
inside our element query. With this in mind, here's how we could rewrite our last example so the style would only apply to the .widget
which meets the min-width: 500px
condition:
@element '.widget' and (min-width: 500px) {
$this h2 {
font-size: 14pt;
}
}
There are a number of additional meta-selectors in the EQCSS syntax that aren't included in regular CSS. The full list is:
$this
$parent
$prev
$next
$it
(inside eval('')
)
$root
These selectors will only work inside styles scoped to an element using an @element
query.
The last and final feature of EQCSS is the most wild of all: eval('')
. Through this doorway lies all the power of JavaScript, accessible from CSS. Though JavaScript can apply styles to elements, it currently has a hard time reaching pseudo-elements like :before
and :after
. But what if CSS could reach JavaScript from the other side, instead of JavaScript settling styles, what if styles could use JavaScript?
Currently it's common to use HTML as an intermediary to allow JavaScript and CSS to communicate with each other. You end up either toggling classes on your HTML to apply different CSS styles, or else adding CSS to HTML in ways that can interfere with your responsive styles. It's difficult to build @media
queries to style elements that also have inline styles effectively.
This is where eval('')
comes in. You can access or evaluate any JavaScript, whether just to refer to the value of a JavaScript variable, to execute a one-liner like new Date().getFullYear()
, or to run a Javascript function on the page and use the value it returns inside your CSS.
@element 'footer' {
$this:after {
content: "© eval('new Date().getFullYear()')";
}
}
$it
inside eval('')
To more easily share the scope of the element query where eval('')
appears, you can use the meta-selector $it
. This replaces the need for something like document.querySelector("$this")
inside eval('')
to find the context of the scoped element. For example, let's say we have a <div>
with the word hello
inside. The following code would output hello hello
:
<div>hello</div>
<style>
@element 'div' {
div:before {
content: 'eval("$it.innerHTML") ';
}
}
</style>
You can also drop the $it
, and unless you're specifying anything else $it
will be implied. The following code performs the exact same task:
@element 'div' {
div:before {
content: 'eval("innerHTML") ';
}
}
eval('')
WorksWhere eval('')
can be useful is in situations when CSS isn't aware of measurements or events that happen on the page after it's loaded. For example, elements like iframe
elements used for embedding Youtube videos come with a specified width and height. While you can set the width to auto, there's no easy way to maintain the correct aspect ratio of width to height as the width changes to fill the width of the container it has been placed inside.
A common workaround to this problem is to place any content that needs to maintain an aspect ratio as it sizes into a wrapper which contains extra padding, based on the aspect ratio of the content. This works well, but requires you to know all of the aspect ratios you need to support in advance, as well as requires extra markup to be added around each video to inform CSS which aspect ratio to use.
The smarter approach to responsive aspect ratios might involve placing each of those iframe
elements into a container without padding applied, and to let JavaScript measure each iframe and calculate each aspect ratio at the time the page loads. Then, JavaScript can apply the correct ratio of padding to each container element to maintain the aspect ratio of the iframe inside.
…But what if CSS could access those measurements directly? Not only could we consolidate our CSS and JS from two separate locations in our codebase into one, but we can also support any aspect ratio we might encounter in the future using one piece of code, without any additional labour, or surrounding containers in our HTML. Here's how simple a wrapper-free responsive video resizing solution could be when using EQCSS syntax:
@element 'iframe' {
$this {
margin: 0 auto;
width: 100%;
height: eval("scrollWidth/(width/height)")px;
}
}
It has the succinctness and legibility of CSS, plus the power of JavaScript. No extra wrappers required, no extra classes, and no extra CSS. In fact, we can make this even more flexible by using more advanced CSS selectors! If you want to add responsive resizing to youtube videos only, you could target only those iframe
elements whose URLs contain "youtube" with a selector like iframe[src*="youtube"]
.
Be careful with eval('')
though, there's a reason why CSS expressions were considered dangerous in the past. If you aren't careful with how many elements you apply them to, or how frequently you recalculate your styles, JavaScript can end up recalculating styles hundreds of times per second. Thankfully, unlike CSS expressions, EQCSS allows you to control this by allowing you to invoke EQCSS.apply()
only when you need to. Future releases of the EQCSS plugin will improve performance by giving developers more control over this process, allowing you to specify which elements need to be recalculated, and which elements are okay to ignore during a recalculation.
Other problems can occur if you create queries with conflicting conditions or styles. EQCSS is like CSS in that it is computed top-to-bottom and using a hierarchy of importance. Although CSS is a stylesheet language, it does contain very advanced capabilities. It's just a couple steps away from being turing-complete as a programming language, but this lack of turing-completeness so far has helped CSS avoid some annoying behaviours more common in programming languages, like recursion and the ability to create infinite loops.
So far, debugging CSS has been a pretty straightforward affair, but EQCSS shifts CSS from simply being a stylesheet language into being an interpreted, dynamic stylesheet language
.
With this new territory comes a variety of potential new problems, but unlike CSS and JavaScript which both have mature debugging tools, when debugging EQCSS you have to make-do with the current tools written for CSS and JavaScript plus a bit of human interpretation to troubleshoot and fix problems. The examples below produce unexpected results and aren't examples of good element queries. Thankfully, writing bad element queries is harder to do than writing good element queries, and I struggled to come up with broken examples of queries that fail.
Here's an example of a reciprocal loop in EQCSS, something which normal CSS @media
queries are immune from by design:
@element '.widget' and (min-width: 300px) {
$this {
width: 200px;
}
}
I call this jekyll: hide;
CSS. But in addition to one style continually triggering itself, there's also the possibility to write multiple queries that trigger each other in what we call the double inverted reciprocal loop, the nastiest of all:
@element '.widget' and (min-width: 400px) {
$this {
width: 200px;
}
}
@element '.widget' and (max-width: 300px) {
.widget {
width: 500px;
}
}
In theory, that unfortunate widget would be stuck resizing between 200px
to 500px
until the end of time, unable to pick a final width. For cases like this, EQCSS simply computes the rules in the order they appear (using normal CSS precedence) and awards the winner. If you rearrange the order these styles appear, the latter style will always win if they are of equal importance.
Some people say that the ability to create loops (or even double-inverted reciprocal loops) is a design flaw, but in order to prevent loops from being possible you would need to limit the power of EQCSS to remove most of the value the syntax provides. It's easy to create infinite loops in JavaScript, and that's not viewed as a design flaw of the language, it's seen evidence of its power. The case is the same with element queries.
This simplest way to make use of element queries is to convert existing designs using @media
queries into @element
queries to liberate
elements and their responsive styles from one layout, and make it easy to re-use that module on other pages or projects. The following @media
query and @element
query might be functionally equivalent.
@media (max-width: 500px) {
.footer a {
display: block;
}
}
@element '.footer' and (max-width: 500px) {
$this a {
display: block;
}
}
The difference is that on the original page, the footer links stayed as display: block
until the browser was at least 500 pixels wide. The second example using element queries would look the same as long as the footer is full-width. But after 'liberating' this footer style we can now also place this footer into a container of any width and be sure that when the footer itself is squished below 500 pixels, the same responsive style applies as in our original.
It only takes minutes to convert CSS designed using @media
queries into @element
queries. Here's what the process looks like:
EQCSS.js
is present in the destination document's HTML
@media
with @element
in your CSS
@element
scope with $this
in case there are multiple instances of that element on the page)
By liberating an element from one design we can isolate it for re-use later. Once you have converted the @media
breakpoints to element-based responsive conditions, you never have to tweak those widths again, no matter where you display it in the future!
@media
queries to @element
queriesDuring the process of supporting IE8, the decision was made to include all valid CSS units a browser understands — that means you can include units like px
, pt
, em
, vw
, vh
, vmin
, vmax
, cm
, mm
, and in
in any width, height, or scroll-based responsive condition EQCSS understands.
If you don't require IE8 support, you're also able to write queries using rem
, ex
, ch
, and pc
units as well. Also, the vw
, vh
, vmin
, vmax
units have been made available via polyfill to let you design with greater accuracy in more browsers.
I have fixed a website navigation problem for IE8 users simply by wrapping the existing problematic CSS in an @element
query. If IE8 support is a big deal to you, EQCSS may be the simplest way to get there.
When designing layouts from scratch with @element
queries, you can easily eliminate much of the labour you might have done when building the same layout using @media
queries, precompilers, and plugins before.
The biggest shift is learning how to stop viewing the DOM only from the perspective of the root HTML element, and think about individual elements on the page being their own responsive universes, sometimes that even interact with each other or change after the page loads as the user continues to interact with the page.
The old paradigms of desktop-first
and mobile-first
aren't relevant any longer, the new way of building interfaces approaches styling element-first
. Using element queries enables you to work on individual parts of layouts in isolation, to style them with a greater level of responsive detail, and to finally realize the principle of 'Atomic Design' at any scale.
Element-first
Element-first design is the spirit of the Atomic design principle, but looks very different in practice than how most people implement Atomic design using their mobile-first
or desktop-first
mindset. Instead of writing styles in advance for every conceivable situation a widget my find itself in, we are able to allow individual parts of the layout to adapt responsively when those elements require it.
It makes more sense to say: These are the styles for the menu when the menu has 300px wide, and these are the styles when the menu is more than 300px wide
than it does to say These styles are in case the menu shows up in a layout outside a wrapper where it has less than 300px space, here’s how the menu shows up in a layout inside a wrapper with less than 300px space, here’s how the menu shows up in a layout without a wrapper but with a sidebar with less than 300px, etc…
and then also defining all those different layout variations at every breakpoint you want to support as well.
You can see why people have turned toward conventions like BEM to try to help them manage the complexity, but we’ve clearly reached a breaking point where our layouts are breaking under the weight of all of the classes and attributes we’re adding to them. We feel the need to use pre-processors which are powerless to help us after the page loads, just to try to output enough CSS just in case
we need it, and in the meantime we have to deal with tons of CSS and find ways to minify and compress that down so it’s small again.
Why not just take an element-first approach? Let’s say you have some HTML like this:
<form>
<input placeholder=Search>
<input type=button value=Search>
</form>
Now let’s say the responsive behaviour you want could be explained like this in english:
Search box and button are side-by-side until they get too narrow, then both the input and the button should be stacked on top of each other and full-width.
In a desktop-first mindset you would write your CSS like this:
input {
width: 100%;
}
@media (max-width: 600px) {
input {
width: 50%;
float: left;
}
}
In a mobile-first mindset, you’re targeting the mobile view (stacked) first and adding support for the side-by-side view only if the screen has enough room:
input {
width: 100%;
}
@media (min-width: 600px) {
input {
width: 50%;
float: left;
}
}
With the first two examples, your @media
breakpoint was set to 600px
not because that’s how wide the inputs will be when they switch. The chances are that search form is probably inside at least one other parent element with margins and padding and so when the browser is 600px
width, those inputs might be somewhere around 550px
or 525px
wide in reality. So in a desktop-first and mobile-first mindset you’re always setting your breakpoints based on the layout and how the elements show up in it. With element-first you would just say: I don’t care how wide the browser may be, I know when my inputs get to be
Instead of using a 525-550px
wide that’s the sweet spot where I want them to stack.@media
query to swap that CSS by the browser’s dimensions, with element-first design you could attach your responsive conditions to the <form>
element and say something like:
input {
width: 100%;
}
@element 'form' and (min-width: 525px) {
input {
width: 50%;
float: left;
}
}
The code is very similar to the previous methods, but now you are free to display this search form in a sidebar, you can make it full-width, you can use it in any template and no matter how wide the browser is, and when the form itself doesn’t have enough room to display the inputs side-by-side it will adapt. Even if the browser is 1000px
wide at the time that the form is squished below 525px
where you’ve put it in your layout.
The best thing we can do for the future of CSS is to experiment with these ideas as much as possible today. No amount of theorizing and thinking about these features is as useful as trying to implement them and discovering the quirks that make them usable or powerful.
In addition to providing a solution for Element Queries, I hope that EQCSS.js
can also serve as a platform for other experiments in extending CSS. If you have an idea of a new responsive condition you'd like to try, a new idea for a selector, or some new feature you want to test out, forking EQCSS.js
gets you most of the way there with no effort.
<script src="http://elementqueries.com/EQCSS.js"></script>
<!--[if lt IE 9]><script src="http://elementqueries.com/EQCSS-polyfills.js"></script><![endif]-->
<script src="http://elementqueries.com/EQCSS.js"></script>
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<meta name=viewport content="width=device-width, initial-scale=1">
<title></title>
<style></style>
</head>
<body>
<!--[if lt IE 9]><script src="http://elementqueries.com/EQCSS-polyfills.js"></script><![endif]-->
<script src="http://elementqueries.com/EQCSS.js"></script>
</body>
</html>
When the body is wider than 600 pixels, make the background red.
<style>
@element 'body' and (min-width: 600px) {
$this {
background: red;
}
}
</style>
When the username input contains more than 15 characters, make the background of the wrapper pink and display the error message
<div>
<span data-notification=success>Username looks good</span>
<span data-notification=error>Username too long</span>
<input>
</div>
<style>
div {
padding: 15px;
}
[data-notification] {
display: none;
}
@element 'input' and (max-characters: 14) {
$parent {
background: lightgreen;
}
$parent [data-notification=success] {
display: block;
}
}
@element 'input' and (min-characters: 15) {
$parent {
background: pink;
}
$parent [data-notification=error] {
display: block;
}
}
</style>
<script>
window.addEventListener('keyup',function(){
EQCSS.apply()
})
</script>
Display an updating character count for the comment box, and when the count hits 50 disable the 'Reply' button and make the border of the comment box red.
<textarea></textarea>
<div></div>
<input type=submit value=Reply>
<style>
@element 'textarea' {
div:after {
content: 'eval("50-value.length") characters left';
display: block;
}
}
@element 'textarea' and (min-characters: 50) {
$this {
border: 1px solid red;
background: pink;
}
div:after {
color: #f00;
font-weight: bold;
}
input[type=submit] {
opacity: .5;
pointer-events: none;
}
}
</style>
<script>
window.addEventListener('keyup',function(){
EQCSS.apply()
})
</script>