CSS Element Queries [Level 1]

A Collection of Interesting Ideas,

This version:
https://github.com/tomhodgins/element-queries-spec
Issue Tracking:
GitHub
Editor:

Abstract

Element Queries allow authors to test and query values or features of elements in an HTML document. They are used in the CSS @element rule to conditionally apply styles to a document, and in various other contexts and languages, such as HTML and JavaScript.

CSS Element Queries [Level 1] describes the mechanism and syntax of scoped styles and element queries, the responsive conditions. It also describes the meta-selectors, functions, and units that make element queries powerful.

1. Introduction

By default all CSS is written from the global scope (:root, or the HTML element), and @media queries apply globally to all elements based on the conditions of the browser and media.

The idea of a scoped style is to allow CSS to view a rule, or multiple rules from the perspective of any element in the document as though it was :root.

Just like CSS already has @media queries which help define styles for different media conditions, this document describes functionality for an @element query syntax to define styles that target elements more specifically with conditions based on their own properties.

1.1. Values

Value types not defined in this specification, such as <integer>, <dimension>, <ratio>, and <ident> are defined in [CSS3VAL], and [MEDIAQUERIES-4]

1.2. Units

The units used in element queries are the same as in other parts of CSS, as defined in [CSS3VAL]. For example, the pixel unit represents CSS pixels and not physical pixels.

Additionally, new element-based units are defined in this document which relate to an element’s own properties after styles have been applied to the document.

2. Element Queries

An element query is a method of testing certain aspects of an element and applying styles based on whether that test is true or false.

The syntax of an element query consists of a <selector-list>, plus an optional list of zero or more responsive conditions, followed by one or more CSS rules:

@element selector list and responsive condition { stylesheet }

A scoped style will return true as long as at least one element in the document matches a selector in the <selector-list>.

An element query is a logical expression that is either true or false. An element query is true if:

User agents must re-evaluate element queries in response to changes in the user environment that they’re aware of, for example resizing the browser, clicking, or scrolling, depending on which responsive conditions are being evaluated, and change the styles applied accordingly.

2.1. Scoped Styles

To write a scoped style, write @element, followed by any amount of whitespace. Wrap one or more comma-separated CSS <selector> in single (') or double (") quotes, followed by any amount of whitespace, and wrap one or more CSS rules in a pair of curly brackets ({,}).

Example scoped style
@element 'html' { ... }

2.1.1. Multiple Selectors

You can include multiple CSS selectors in your scoped style by separating them with a comma and any amount of whitespace.

Comma-separated selector list
@element 'ul, ol' { ... }

2.1.2. Understanding the Scope

Any element on the page that matches a selector will apply the rules contained inside its scope to the page. The following examples will illustrate how the scope applies.

If we have two div elements in our HTML:

<div></div>
<div class=demo></div>
@element 'div' {
  div {
    background: lime;
  }
}

In this example, because there are two div elements, both will match our scope and apply a rule saying all div elements have a green background.

@element '.demo' {
  div {
    background: lime;
  }
}

In this example we have one element in our HTML with a class of .demo. Because of this, the rule applying to all div elements applies to both of the div elements in our HTML turning their background green.

@element '.demo' {
  .demo {
    background: lime;
  }
}

In this example we have one element in our HTML with a class of .demo. Because of this, the rule applying to any elements with a class of .demo applies and turns the background of our .demo element green, leaving the other div untouched.

@element 'div' {
  .demo {
    background: lime;
  }
}

In this example, because there are two div elements, both will match our scope and apply a rule saying any element with a class of .demo will have a green background. This makes one of our div elements green and leaves the other untouched.

@element 'select' {
  .demo {
    background: lime;
  }
}

In this example we did not have any select elements in our HTML, so nothing will match our scope and no styles will be applied, leaving both of our div elements untouched.

It is possible for an element to match more than one @element query.

2.2. Scoped Styles with Responsive Conditions

An element query is a scoped style with one or more responsive conditions added.

The following global CSS and scoped style are equivalent.

Example of global CSS
body {
  background: lime;
}
Example scoped style
@element 'html' {
  body {
    background: lime;
  }
}

In both cases, as long as there is a body element inside of our html, it will have a lime background.

We can also add a responsive conditions to our scoped styles. To do this, write and followed by a responsive condition and value, separated by a colon (:) and wrapped in brackets ((,)).

Example Element Query with Responsive Condition
@element 'html' and (min-width: 500px) {
  body {
    background: lime;
  }
}

In this case, our element query is equivalent to the following media query, but not every element query will be able to be expressed as a media query.

Example media query with responsive condition
@media (min-width: 500px) {
  body {
    background: lime;
  }
}

2.2.1. Multiple Conditions

You can add more than one responsive conditions to your element query. For this, include another and, followed by another responsive condition as before.

Element query with multiple responsive conditions
@element 'html' and (min-width: 500px) and (max-width: 1000px) {
  body {
    background: lime;
  }
}

Which in this case can be compared to the equivalent media query.

Media query with multiple responsive conditions
@media (min-width: 500px) and (max-width: 1000px) {
  body {
    background: lime;
  }
}

In both cases when our html is between the sizes of 500px and 1000px, our body will have a lime background.

2.2.2. Combining @element and @media queries

It’s possible to combine the use of scoped styles or element queries with media queries. Most of the time we combine them we want to include the media query on the inside of the element query. This will mean any time the element query is true, that media query will be visible to the browser and apply.

Nesting a media query inside an element query
@element '#headline' and (min-characters: 20) {
  @media print {
    #headline {
      border: 1px solid black;
    }
  }
}

In this example, if the element with an ID of #headline has over 20 characters, it will display in print media with a thin black border.

2.2.3. Self-Referential @element queries

With element queries comes the new possibility that the rules you are applying when your responsive conditions are met will conflict with the responsive condition in your element query. In this situation we should not attempt to detect or handle these cases by ignoring certain styles, or checking whether any of the rules affect the validity of the responsive condition. It is best to apply the rule naïvely and allow the conflict to occur.

Note, The responsibility lies with the author to write @element queries which can be interpreted logically.

@element '.widget' and (min‐width: 300px) {
  $this {
    width: 200px;
  }
}

In this example the query will only apply to elements with a class of .widget that are equal or wider than 300px, and the rule sets a width on the same element which would make the rule no longer apply. Rather than trying to detect or intercept this behaviour and prevent it from happening, when the @element query applies to the document it will set the width to 200px.

2.2.4. Circular Referential @element queries

Another new possibility with element queries is having two or more queries that apply styles that match each others responsive condition. Just as with a self-referential @element query, we should not try to detect or ignore styles in this situation, and instead should compute the @element queries in the order they appear and apply the rules using normal CSS specificity.

@element '.widget' and (min‐width: 400px) {
  $this {
    width: 200px;
  }
}
@element '.widget' and (max‐width: 300px) {
  $this {
    width: 500px;
  }
}

In this example any element with a class of .widget that is equal to 400px or wider will apply a rule setting the width of the same element to 200px, which makes the following @element query valid. The second query sets a width of 500px on the same element. In this case, even though the element now matches the responsive condition of the first query again, rather than get stuck in an infinite loop, we consider evaluation complete.

3. Syntax

Informal descriptions of the element query syntax appear in the prose and railroad diagrams of the previous sections. The formal element query syntax is described in this section, with the rule/property gramma syntax defined in [CSS3SYN] and [CSS3VAL].

@element = @element <eq-prelude> { <stylesheet> }

<eq-prelude> = <selector-list> | <selector-list> <eq-condition>*

<eq-condition> = and ( <eq-name> : <eq-value> )

<eq-name> = 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 | orientation
  | min-aspect-ratio | max-aspect-ratio

<eq-value> = <integer> | <dimension> | <ratio> | <ident>

meta-selectors = $this | $parent | $prev | $next | $it

eq-unit = ew | eh | emin | emax

eval("") = eval(" javascript-code ")

3.1. Evaluating Element Queries

Each <eq-condition> is associated with a boolean result, the same as the result of evaluating the specified responsive condition.

If the result of any responsive condition is used in any context that expects a two-valued boolean, "unknown" must be converted to "false".

Note: This means that, for example, when an element query is used in an @element rule, if it resolves to "unknown" it is treated as "false" and fails to match.

4. Meta-Selectors

With element queries comes the need to target elements based on the scope we have defined. There are a number of new selectors that help target elements relative to the scope we have defined. These new selectors only work inside a scoped style or element query and are called meta-selectors.

4.1. Diagram of meta-selectors

Here is a diagram showing the relationship between the meta-selectors. The $parent is the parent element of $this, and $prev and $next represent the adjacent siblings to $this.

$parent$prev$this$next
$this
The $this meta-selector refers each element at the root of our scoped style when it matches the responsive condition.
Example of $this meta-selector
@element '.widget' and (min-width: 200px) {
  $this {
    background: lime;
  }
}

In this case, any element with a class of .widget that is 200px or wider will have a green background.

$parent
The $parent meta-selector refers to the element containing the element(s) in the scope of our scoped style or element query.
Example of $parent meta-selector
@element '.widget' and (min-width: 200px) {
  $parent {
    background: lime;
  }
}

In this case, any element containing an element with a class of .widget that is equal or wider than 200px will have a green background.

$prev
The $prev meta-selector refers to the sibling directly above the element at the root of our scoped style or element query.
Example of $prev meta-selector
@element '.widget' {
  $prev {
    background: lime;
  }
}

In this case, any sibling coming directly before any element with a class of .widget will have a green background.

$next
The $next meta-selector refers to the sibling directly below the element at the root of our scoped style or element query.
Example of $next meta-selector
@element '.widget' {
  $next {
    background: lime;
  }
}

In this case, any sibling coming directly after any element with a class of .widget will have a green background.

Note, the $next is similar to the selector: $this + *

5. Responsive Conditions

All responsive conditions for @element queries are formatted as a condition name and value, separated by a colon (:) character, surrounded by brackets (()).

and ( condition name : value )

The following examples are all valid as <eq-condition>:

(min-width: 500px)
(min-aspect-ratio: 16/9)
(orientation: landscape)

5.1. Width

Name: width
For: @element
Value: <dimension>
Type: range

The min-width responsive condition applies to any scoped element that has greater or equal (>=) width to the specified value.

Example min-width element query
@element '.widget' and (min-width: 200px) {
  $this {
    background: lime;
  }
}

In this case any element with a class of .widget that is at least 200px or wider will have a green background.

The max-width responsive condition applies to any scoped element that has lesser or equal (<=) width to the specified value.

Example max-width element query
@element '.widget' and (max-width: 200) {
  $this {
    background: lime;
  }
}

In this case any element with a class of .widget that 200px or narrower will have a green background.

5.2. Height

Name: height
For: @element
Value: <dimension>
Type: range

The min-height responsive condition applies to any scoped element that has greater or equal (>=) height to the specified value.

Example min-height element query
@element '.widget' and (min-height: 50px) {
  $this {
    background: lime;
  }
}

In this case any element with a class of .widget that is at least 50px or taller will have a green background.

The max-height responsive condition applies to any scoped element that has lesser or equal (<=) height to the specified value.

Example max-height element query
@element '.widget' and (max-height: 50px) {
  $this {
    background: lime;
  }
}

In this case any element with a class of .widget that is at least 50px or shorter will have a green background.

5.3. Characters

Name: characters
For: @element
Value: <integer>
Type: range

The min-characters responsive condition applies to any scoped element that contains a greater or equal (>=) number of characters.

Example min-characters element query
@element 'input' and (min-characters: 5) {
  $this {
    background: lime;
  }
}

In this case any input with 5 or more characters will have a green background.

The max-characters responsive condition applies to any scoped element that contains lesser or equal (<=) number of characters specified.

Example max-characters element query
@element 'input' and (max-characters: 5) {
  $this {
    background: lime;
  }
}

In this case any input with 5 or fewer characters will have a green background.

5.4. Lines

Name: lines
For: @element
Value: <integer>
Type: range

The min-lines responsive condition applies to any scoped element that contains greater or equal (>=) number of specified lines of text.

Example min-lines element query
@element 'textarea' and (min-lines: 3) {
  $this {
    background: lime;
  }
}

In this case any textarea with 3 or more lines will have a green background.

The max-lines responsive condition applies to any scoped element that contains lesser or equal (<=) number of specified lines of text.

Example max-lines element query
@element 'textarea' and (max-lines: 3) {
  $this {
    background: lime;
  }
}

In this case any textarea with 3 or fewer lines will have a green background.

5.5. Children

Name: children
For: @element
Value: <integer>
Type: range

The min-children responsive condition applies to any scoped element that contains greater or equal (>=) number of child elements specified.

Example min-children element query
@element '.social-icons' and (min-children: 5) {
  $this {
    background: lime;
  }
}

In this case any element with a class of .social-icons that contains more 5 or more direct descendants will have a green background.

The max-children responsive condition applies to any scoped element that contains lesser or equal (<=) number of child elements specified.

Example max-children element query
@element '.social-icons' and (max-children: 5) {
  $this {
    background: lime;
  }
}

In this case any element with a class of .social-icons that contains more 5 or fewer direct descendants will have a green background.

5.6. Scroll-Y

Name: scroll-y
For: @element
Value: <dimension>
Type: range

The min-scroll-y responsive condition applies to any scoped element that has scrolled a greater or equal (>=) amount to the value specified in a vertical direction.

Example min-scroll-y element query
@element '.feed' and (min-scroll-y: 100px) {
  $this {
    background: lime;
  }
}

In this case any element with a class of .feed that has scrolled 100px or more vertical will have a green background.

The max-scroll-y responsive condition applies to any scoped element that has scrolled a lesser or equal (<=) amount to the value specified in a vertical direction.

Example max-scroll-y element query
@element '.feed' and (max-scroll-y: 100px) {
  $this {
    background: lime;
  }
}

In this case any element with a class of .feed that has scrolled 100px or less vertically will have a green background.

5.7. Scroll-X

Name: scroll-x
For: @element
Value: <dimension>
Type: range

The min-scroll-x responsive condition applies to any scoped element that has scrolled a greater or equal (>=) amount to the value specified in a horizontal direction.

Example min-scroll-x element query
@element '.feed' and (min-scroll-x: 100px) {
  $this {
    background: lime;
  }
}

In this case any element with a class of .feed that has scrolled 100px or more horizontally will have a green background.

The max-scroll-x responsive condition applies to any scoped element that has scrolled a lesser or equal (<=) amount to the value specified in a horizontal direction.

Example max-scroll-x element query
@element '.feed' and (max-scroll-x: 100px) {
  $this {
    background: lime;
  }
}

In this case any element with a class of .feed that has scrolled 100px or less horizontally will have a green background.

5.8. Aspect-Ratio

Name: aspect-ratio
For: @element
Value: <ratio>
Type: range

The min-aspect-ratio responsive condition applies to any scoped element with a greater or equal (>=) aspect ratio, specified as a width and height pair, separated by a slash (/).

Example min-aspect-ratio element query
@element '.widget' and (min-aspect-ratio: 16/9) {
  $this {
    background: lime;
  }
}

In this case any element that had an aspect ratio of 16/9 or greater would have a green background.

The max-aspect-ratio responsive condition applies to any scoped element with a lesser or equal (<=) aspect ratio, specified as a width and height pair, separated by a slash (/).

Example max-aspect-ratio element query
@element '.widget' and (max-aspect-ratio: 16/9) {
  $this {
    background: lime;
  }
}

In this case any element that had an aspect ratio of 16/9 or lesser would have a green background.

5.9. Orientation

Name: orientation
For: @element
Value: portrait | square | landscape
Type: discrete

The orientation responsive condition applies to any scoped element that matches the orientation specified. The following orientations are included: landscape, square, portrait

5.9.1. Portrait Orientation

portrait
The orientation is portrait if the scoped element has a greater (>) height than width
Element query for portrait orientation
@element '.widget' and (orientation: portrait) {
  $this {
    background: lime;
  }
}

In this case any element with a class of .widget that is narrower than it is tall will have a green background.

5.9.2. Square Orientation

square
The orientation is square if the scoped element has an equal (=) height and width
Element query for square orientation
@element '.widget' and (orientation: square) {
  $this {
    background: lime;
  }
}

In this case any element with a class of .widget that has an equal width and height will have a green background.

5.9.3. Landscape Orientation

landscape
The orientation is landscape if the scoped element has a greater (>) width than height
Element query for landscape orientation
@element '.widget' and (orientation: landscape) {
  $this {
    background: lime;
  }
}

In this case any element with a class of .widget that is wider than it is tall will have a green background.

6. CSS Functions

6.1. eval("")

Note, Much of the functionality of this feature is similar to CSS variables, however being able to execute JavaScript from the vantage point of the element in the scope could perhaps be a way that CSS variables could be used a little differently inside scoped styles or element queries.

The eval("") function can be used anywhere inside a scoped style or element query. It shares a scope with the element in the root of the scoped style.

eval(" javascript ")

When using eval("") you can write eval, followed by a pair of brackets ((,)) which contain JavaScript code, wrapped in either single (') or double (") quotes.

This JavaScript can do something simple like reference the value of a variable, perform a simple calculation inline, or it can use the value return by a function.

Note, Unlike CSS variables, the eval("") function can be used anywhere in CSS: as a value, a property name, a selector, a '@media' query breakpoint - it can be used for anything, as long as it is inside of a scoped style.

6.1.1. Using JavaScript Variables in CSS

Accessing a JavaScript variable with eval("")
<script>
  var brandColor = 'lime'
</script>

<style>
  @element '.widget' {
    $this {
      background: eval("brandColor");
    }
  }
</style>

In this case any element with a class of .widget will have a green background.

6.1.2. Writing JavaScript in CSS

Evaluating JavaScript Inline with eval("")
@element '.widget' {
  font-size: eval("10 * 2")px;
}

In this case our inline JavaScript (10 * 2) evaluates in place, leaving us with a font size of 20px.

6.1.3. Using JavaScript Functions in CSS

Using Values Returned from Functions with eval("")
<script>
  function addTen(number){
    var number = parseInt(number) || 0
    return number + 10
  }
</script>

<style>
  @element '.widget' {
    font-size: eval("addTen(15)")px;
  }
</style>

In this case any element with a class of .widget will have a font size of 25px.

6.1.4. Replacing Values in CSS

Here is eval("") being used as a value:
<script>
  var blue = '#07f'
</script>

<style>
  @element 'div' {
    $this {
      color: eval("blue");
    }
  }
</style>

This example would be equivalent to: div { color: #07f; }

6.1.5. Replacing Properties in CSS

Here is eval("") being used as a property:
<input>
<script>
  var browser = /Safari/.test(navigator.userAgent) ? '-webkit-':''
</script>

<style>
  @element 'input' {
    $this {
      eval('browser')appearance: none;
    }
  }
</style>

This example would use the property: -webit-appearance if the browser was Safari, otherwise would use: appearance.

6.1.6. Replacing Selectors in CSS

Here is eval("") replacing a list of selectors:
<script>
  var buttonish = '[type=button], [type=submit], [type=reset], button'
</script>

<style>
  @element 'html' {
    eval('buttonish') {
      appearance: none;
      color: black;
      background: white;
      border: 1px solid currentColor;
    }
  }
</style>

This example would be equivalent the following rule:

[type=button], [type=submit], [type=reset], button {
  appearance: none;
  color: black;
  background: white;
  border: 1px solid currentColor;
}

Check the Using Variables as Selectors example below for a more elaborate demonstration, including pseudo-classes like :hover or :focus

Note, Because eval("") works everywhere in CSS inside of a scoped style, it’s also possible to use inside the content property for pseudo-elements like before and after.

6.1.7. $it selector

$it
By default the inline evaluation of eval("") shares the same scope in JavaScript as the scoped element. This means for a scoped style like @element '.widget' {}, things like innerHTML represent the innerHTML of the elements with .widget. This would also apply individually to each element if more than one element on the page matches that selector. In this example we can use parentNode inside of eval("") to check how many child elements the parent element holding any .widget element contains.
Example of eval("") with Implicit Context
@element '.widget' {
  $this:before {
    content: 'eval("parentNode.children.length")';
  }
}

In this case the pseudo-element :before the content of any element with a class of .widget will contain text of the number of child elements inside the parent element of our .widget element.

The meta-selector $it works inside eval("") as a placeholder for the scoped element. The following example is functionally equivalent to the last example:

Example of eval("") with the $it Selector
@element '.widget' {
  $this:before {
    content: 'eval("$it.parentNode.children.length")';
  }
}

7. Element-based Units

Just as CSS has viewport-percentage units: vw, vh, vmin, and vmax which represent a distance equal to 1% of the viewport width, height, shortest edge, and longest edge, in a similar way the following element-percentage units ew, eh, emin, and emax represent a distance equal to 1% of the element’s own width, height, shortest edge, and longest edge. When the width of height of the element changes, the value of these units scale accordingly.

Note, These new eq-unit units always refer to the dimensions of the element in the scope, and can be used inside of a scoped style or element query to style other elements.

ew unit
Equal to 1% of the width of the scoped element
Example of ew Units Inside a Scoped Style
@element '.widget' {
  $this {
    font-size: 10ew;
  }
}

In this case the font size of any element with a class of .widget is equal to 10% of the scoped element’s width.

Note, This unit is similar to the vw viewport unit, but based on the scoped element’s dimensions.

eh unit
Equal to 1% of the height of the scoped element
Example of eh Units Inside a Scoped Style
@element '.widget' {
  $this {
    font-size: 10eh;
  }
}

In this case the font size of any element with a class of .widget is equal to 10% of the scoped element’s height.

Note, This unit is similar to the vh viewport unit, but based on the scoped element’s dimensions.

emin unit
Equal to the smaller of ew or eh
Example of emin Units Inside a Scoped Style
@element '.widget' {
  $this {
    font-size: 10emin;
  }
}

In this case the font size of any element with a class of .widget is equal to 10% of the scoped element’s shortest edge.

Note, This unit is similar to the vmin viewport unit, but based on the scoped element’s dimensions.

emax unit
Equal to the larger of ew or eh
Example of emax Units Inside a Scoped Style
@element '.widget' {
  $this {
    font-size: 10emax;
  }
}

In this case the font size of any element with a class of .widget is equal to 10% of the scoped element’s longest edge.

Note, This unit is similar to the vmax viewport unit, but based on the scoped element’s dimensions.

8. Examples of Scoped Styles & Element Queries

8.1. Examples of valid scoped styles

@element 'div' {}
@element 'div, span' {}
@element 'div' and (min-width: 500px) {}
@element 'div' and (min-width: 500px) and (min-lines: 3) {}

8.2. Wrapper-free responsive iframe resize

<iframe width="640" height="360"></iframe>
<style>
  @element 'iframe' {
    $this {
      width: 100%;
      height: eval('offsetWidth / (width / height)')px;
    }
  }
</style>

In this example the iframe element has a width="" and height="" attribute supplied in HTML which is being accessed from CSS using $it.width and $it.height inside our eval("") function. Because we are able to compute the HTML attributes directly, this one rule will make multiple iframe elements with different attribute values on the same page responsive, as it computes each iframe separately as $this.

8.3. Faking input:empty

<input>
<style>
  /* input:empty */
  @element 'input' and (max-characters: 0) {
    $this {
      background: red;
    }
  }
  /* input:not(:empty) */
  @element 'input' and (min-characters: 1) {
    $this {
      background: lime;
    }
  }
</style>

In this example we are able to apply styles based on whether there are zero or at least one characters inside any input element. CSS has the pseudo-class :empty, however it doesn’t apply to all elements (like input elements) so by using max-characters and min-characters with element queries we are able to express and style a similar idea.

8.4. Auto-expanding textarea

<textarea></textarea>
<style>
  @element 'textarea' {
    $this {
      height: eval("style.height='inherit';style.height=scrollHeight+'px'");
    }
  }
</style>

In this example we are using eval("") to reset the height value of our element and immediately set it to the scrollHeight of the element, ensuring that no matter how many lines of text it contains, it will always expand & contract to contain all of them.

8.5. Display Browser Dimensions

<style>
  @element ':root' {
    $this:before {
      content: 'eval("window.innerWidth") × eval("window.innerHeight")';
      position: fixed;
      top: .5em;
      left: .5em;
    }
  }
</style>

This example will create a new pseudo-element on our ':root' element (the HTML element) and uses eval("") to display the innerWidth and innerHeight values of our window.

8.6. Self-responsive Grid

<h2>Responsive Grid</h2>
<p>Small = full width, Medium = thirds, Large = sixths.</p>
<section data-grid>
  <div class="col-10 medium-split-3 large-split-6"></div>
  <div class="col-10 medium-split-3 large-split-6"></div>
  <div class="col-10 medium-split-3 large-split-6"></div>
  <div class="col-10 medium-split-3 large-split-6"></div>
  <div class="col-10 medium-split-3 large-split-6"></div>
  <div class="col-10 medium-split-3 large-split-6"></div>
</section>
<h2>Split Grid</h2>
<p>Small = half width, Medium = 1, 2, 3, 4 split, Large = 4, 3, 2, 1 split.</p>
<section data-grid>
  <div class="col-5 medium-split-1 large-split-4"></div>
  <div class="col-5 medium-split-2 large-split-4"></div>
  <div class="col-5 medium-split-2 large-split-4"></div>
  <div class="col-5 medium-split-3 large-split-4"></div>
  <div class="col-5 medium-split-3 large-split-3"></div>
  <div class="col-5 medium-split-3 large-split-3"></div>
  <div class="col-5 medium-split-4 large-split-3"></div>
  <div class="col-5 medium-split-4 large-split-2"></div>
  <div class="col-5 medium-split-4 large-split-2"></div>
  <div class="col-5 medium-split-4 large-split-1"></div>
</section>
<h2>Hidden Elements</h2>
<p>Small = Hide Small, Medium = Hide Medium, Large = Hide Large.</p>
<section data-grid>
  <div class="col-10 hide-small">Small</div>
  <div class="col-10 hide-medium">Medium</div>
  <div class="col-10 hide-large">Large</div>
</section>
<style>
  * {
    box-sizing: border-box;
  }
  [data-grid] div {
    min-height: 50px;
    background: lime;
    border: 5px solid white;
  }
  /* Element Query Grid */
  @element '[data-grid]' {
    $this,
    $this * {
      box-sizing: border-box;
    }
    $this:after,
    $this [data-row]:after {
      content: '';
      display: block;
      clear: both;
    }
    $this [class*=-col-],
    $this [class*=-split-] { float: left; }
    $this [class^=col-1] { width: 10%; }
    $this [class^=col-2] { width: 20%; }
    $this [class^=col-3] { width: 30%; }
    $this [class^=col-4] { width: 40%; }
    $this [class^=col-5] { width: 50%; }
    $this [class^=col-6] { width: 60%; }
    $this [class^=col-7] { width: 70%; }
    $this [class^=col-8] { width: 80%; }
    $this [class^=col-9] { width: 90%; }
    $this [class^=col-10] { width: 100%; }
    $this [class^=split-1] { width: calc(100%/1); }
    $this [class^=split-2] { width: calc(100%/2); }
    $this [class^=split-3] { width: calc(100%/3); }
    $this [class^=split-4] { width: calc(100%/4); }
    $this [class^=split-5] { width: calc(100%/5); }
    $this [class^=split-6] { width: calc(100%/6); }
    $this [class^=split-7] { width: calc(100%/7); }
    $this [class^=split-8] { width: calc(100%/8); }
    $this [class^=split-9] { width: calc(100%/9); }
    $this [class^=split-10] { width: calc(100%/10); }
  }
  @element '[data-grid]' and (max-width: 400px) {
    $this [class*=small-col-1] { width: 10%; }
    $this [class*=small-col-2] { width: 20%; }
    $this [class*=small-col-3] { width: 30%; }
    $this [class*=small-col-4] { width: 40%; }
    $this [class*=small-col-5] { width: 50%; }
    $this [class*=small-col-6] { width: 60%; }
    $this [class*=small-col-7] { width: 70%; }
    $this [class*=small-col-8] { width: 80%; }
    $this [class*=small-col-9] { width: 90%; }
    $this [class*=small-col-10] { width: 100%; }
    $this [class*=small-split-1] { width: calc(100%/1); }
    $this [class*=small-split-2] { width: calc(100%/2); }
    $this [class*=small-split-3] { width: calc(100%/3); }
    $this [class*=small-split-4] { width: calc(100%/4); }
    $this [class*=small-split-5] { width: calc(100%/5); }
    $this [class*=small-split-6] { width: calc(100%/6); }
    $this [class*=small-split-7] { width: calc(100%/7); }
    $this [class*=small-split-8] { width: calc(100%/8); }
    $this [class*=small-split-9] { width: calc(100%/9); }
    $this [class*=small-split-10] { width: calc(100%/10); }
    $this [class*=hide-small] { display: none; }
  }
  @element '[data-grid]' and (min-width: 400px) and (max-width: 800px) {
    $this [class*=medium-col-1] { width: 10%; }
    $this [class*=medium-col-2] { width: 20%; }
    $this [class*=medium-col-3] { width: 30%; }
    $this [class*=medium-col-4] { width: 40%; }
    $this [class*=medium-col-5] { width: 50%; }
    $this [class*=medium-col-6] { width: 60%; }
    $this [class*=medium-col-7] { width: 70%; }
    $this [class*=medium-col-8] { width: 80%; }
    $this [class*=medium-col-9] { width: 90%; }
    $this [class*=medium-col-10] { width: 100%; }
    $this [class*=medium-split-1] { width: calc(100%/1); }
    $this [class*=medium-split-2] { width: calc(100%/2); }
    $this [class*=medium-split-3] { width: calc(100%/3); }
    $this [class*=medium-split-4] { width: calc(100%/4); }
    $this [class*=medium-split-5] { width: calc(100%/5); }
    $this [class*=medium-split-6] { width: calc(100%/6); }
    $this [class*=medium-split-7] { width: calc(100%/7); }
    $this [class*=medium-split-8] { width: calc(100%/8); }
    $this [class*=medium-split-9] { width: calc(100%/9); }
    $this [class*=medium-split-10] { width: calc(100%/10); }
    $this [class*=hide-medium] { display: none; }
  }
  @element '[data-grid]' and (min-width: 800px) {
    $this [class*=large-col-1] { width: 10%; }
    $this [class*=large-col-2] { width: 20%; }
    $this [class*=large-col-3] { width: 30%; }
    $this [class*=large-col-4] { width: 40%; }
    $this [class*=large-col-5] { width: 50%; }
    $this [class*=large-col-6] { width: 60%; }
    $this [class*=large-col-7] { width: 70%; }
    $this [class*=large-col-8] { width: 80%; }
    $this [class*=large-col-9] { width: 90%; }
    $this [class*=large-col-10] { width: 100%; }
    $this [class*=large-split-1] { width: calc(100%/1); }
    $this [class*=large-split-2] { width: calc(100%/2); }
    $this [class*=large-split-3] { width: calc(100%/3); }
    $this [class*=large-split-4] { width: calc(100%/4); }
    $this [class*=large-split-5] { width: calc(100%/5); }
    $this [class*=large-split-6] { width: calc(100%/6); }
    $this [class*=large-split-7] { width: calc(100%/7); }
    $this [class*=large-split-8] { width: calc(100%/8); }
    $this [class*=large-split-9] { width: calc(100%/9); }
    $this [class*=large-split-10] { width: calc(100%/10); }
    $this [class*=hide-large] { display: none; }
  }
</style>

This example uses @element queries to apply the different responsive styles to our grid when the grid element itself reaches certain breakpoints. Because the styles are scoped to each grid using $this, we can use these same styles to power many grids in the same layout all using the same styles.

8.7. Using Variables as Selectors

<input type=hidden placeholder=hidden>
<input type=text placeholder=text>
<input type=search placeholder=search>
<input type=tel placeholder=tel>
<input type=url placeholder=url>
<input type=email placeholder=email>
<input type=password placeholder=password>
<input type=date placeholder=date>
<input type=month placeholder=month>
<input type=week placeholder=week>
<input type=time placeholder=time>
<input type=datetime-local placeholder=datetime-local>
<input type=number placeholder=number>
<input type=color placeholder=color>
<textarea>textarea</textarea>
<script>
  var textish = [
    '[type=text]', '[type=search]', '[type=tel]', '[type=url]', '[type=email]', '[type=password]', '[type=date]', '[type=month]', '[type=week]', '[type=time]', '[type=datetime-local]', '[type=number]', '[type=color]', 'textarea'
  ]
  /* return selectors with :states using action(array,state) */
  function action(array,state){
    var selectors = '';
    for(i=0; i<array.length; i++){
      selectors += array[i]+':'+state
      if (i < array.length-1){
        selectors += ', '
      }
    }
    return selectors
  }
</script>
<style>
  /* set width for most inputs */
  @element ':root' {
    /* Style all selectors in 'textish' array */
    eval('textish') {
      display: block;
      width: 100%;
    }
    /* Add ':hover' to all selectors in 'textish' array */
    eval('action(textish,"hover")') {
      background: lime;
    }
    /* Add ':focus' to all selectors in 'textish' array */
    eval('action(textish,"focus")') {
      background: red;
    }
  }
</style>

In this example we use eval("") to return either a list of selectors (stored as strings in a JavaScript array) or we use the return from a function that allows us to append strings like ':hover', or ':focus' to each selector as it gives them to use as a selector or selector list in our CSS inside of a scoped style. For manipulating or styling many similar things (like text-style inputs but not other inputs) we can simplify the selectors we need to use throughout our CSS while maintaining full control over what gets styled.

8.8. Clamped Numbers in CSS

<h1>Headline</h1>
<script>
  function clamp(min,mid,max){
    return Math.min(Math.max(min,mid),max)
  }
</script>
<style>
  @element 'h1' {
    $this {
      font-size: eval('clamp(50,innerWidth/10,100)')px;
    }
  }
</style>

In this example our font size prefers to be equal to window.innerWidth/10 pixels wide, but our JavaScript function will clamp that value for us, ensuring that if innerWidth/10 is less than 50, our font size will go no lower than 50px, and if innerWidth/10 is greater than 100, our font size will grow no larger than 100px.

Conformance

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSS-CONDITIONAL-3]
CSS Conditional Rules Module Level 3 URL: https://drafts.csswg.org/css-conditional-3/
[CSS3SYN]
Tab Atkins Jr.; Simon Sapin. CSS Syntax Module Level 3. URL: http://dev.w3.org/csswg/css-syntax/
[CSS3VAL]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 3. 29 September 2016. CR. URL: https://www.w3.org/TR/css-values-3/
[MEDIAQUERIES-4]
Florian Rivoal; Tab Atkins Jr.. Media Queries Level 4. URL: https://drafts.csswg.org/mediaqueries-4/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[SELECTORS-4]
Selectors Level 4 URL: https://drafts.csswg.org/selectors-4/

Informative References

[CSS-CONTENT-3]
Elika Etemad; Dave Cramer. CSS Generated Content Module Level 3. URL: https://drafts.csswg.org/css-content/

Property Index

No properties defined.

@element Descriptors

Name Value Initial Type
width <dimension> range
height <dimension> range
characters <integer> range
lines <integer> range
children <integer> range
scroll-y <dimension> range
scroll-x <dimension> range
aspect-ratio <ratio> range
orientation portrait | square | landscape discrete