CSS Nesting Module Level 3

Editor’s Draft, 24 January 2014

This version:
http://tabatkins.github.io/specs/css-nesting/
Editor’s Draft:
http://tabatkins.github.io/specs/css-nesting/
Test Suite:
None Yet
Editors:
Tab Atkins (Google)

Abstract

This module introduces the ability to nest one style rule inside another, with the selector of the child rule relative to the selector of the parent rule. This increase the modularity and maintainability of CSS stylesheets.

Status of this document

This is an unofficial draft. It is provided for discussion only and may change at any moment. Its publication here does not imply endorsement of its contents by W3C. Don’t cite this document other than as work in progress.

Table of contents

1 Introduction

This section is not normative.

This module describes support for nesting a style rule within another style rule, allowing the inner rule’s selector to reference the elements matched by the outer rule. This feature allows related styles to be aggregated into a single structure within the CSS document, improving readability and maintainability.

1.1 Module Interactions

This module introduces new parser rules that extend the [CSS21] parser model. This module introduces selectors that extend the [SELECTORS4] module.

1.2 Values

This specification does not define any new properties or values.

1.3 Motivation

CSS Rules for even moderately complicated web pages include lots of duplication for the purpose of styling related content. For example, here is a portion of the CSS markup for one version of the [CSS3COLOR] module:

  table.colortable td {
    text-align:center;
  }
  table.colortable td.c {
    text-transform:uppercase;
  }
  table.colortable td:first-child, table.colortable td:first-child+td {
    border:1px solid black;
  }
  table.colortable th {
    text-align:center;
    background:black;
    color:white;
  }

Nesting allow the grouping of related style rules, like this:

  table.colortable {{
    td {
      text-align:center;
      {&.c { text-transform:uppercase }}
      {&:first-child, &:first-child + td { border:1px solid black }}
    }
    th {
      text-align:center;
      background:black;
      color:white;
    }
  }}

Besides removing duplication, the grouping of related rules improves the readability and maintainability of the resulting CSS.

2 The Nesting Block

Style rules cannot be nested directly inside of style rules, as selectors are ambiguous with properties on a syntax level. Instead, the nesting block, which is simply a pair of "{}" characters surrounding the nested rules, is used. The nesting block separates the nested rules from surrounding properties so that they can be parsed unambiguously.

A nesting block is valid inside of style rules and style attributes, at the same level as declarations.

The selectors of style rules inside a nesting block are relative to the results of the selector of the parent rule. Effectively, nesting is equivalent to appending the parent and child selectors with a descendant selector, handling selector lists appropriately.

Why is the nesting block needed?

The idea of nesting came from CSS preprocessors, but none of them need something like a nesting block in order to nest. Generally, they let you mix properties and selectors without reservation. So why do we need the nesting block here?

In general, selectors and properties are grammatically ambiguous with each other. For example, foo:hover bar baz qux... looks like both a selector, and a "foo" property with hover bar baz qux as its value. In some cases, you can’t tell the difference between the two until you hit a semicolon or curly brace, which may be an arbitrary number of tokens away.

Preprocessors are okay with needing arbitrary lookahead to parse CSS. Browsers aren’t - they want fast, parallizable parsing, and that means fixed, small amounts of lookahead.

The nesting block lets the browser immediately tell that it’s not looking at a property, so it can switch into a selector-parsing mode right away.

The following example using Nesting:
  div, p {{
    .keyword, .constant {color: red;}
  }}

...produces the same results as the following CSS:

  div .keyword, div .constant, p .keyword, p .constant {
    color:red;
  }

Multiple nesting blocks can be embedded within a style rule, and can be embedded arbitrarily deeply. Nesting blocks and properties can both appear in a single declaration block, and in any order.

The following example using Nesting:
  div, p {
    font-size: 10px;
    {.keyword {color: green;}}
    {.constant {
      color: red;
      background-color: green;
      {&:hover::after { content: " [" attr(value) "]";}}
    }}
  }

...produces the same results as the following CSS:

  div, p {font-size: 10px;}
  div .keyword, p .keyword {color: green;}
  div .constant, p .constant {color: red; background-color: green;}
  div .constant:hover::after, p .constant:hover::after {content: " [" attr(value) "]";}

Note: Though it’s unlikely that stylesheets authored with Nesting will be useful in legacy user agents, as the nested sections will be ignored by the error-handling rules, authors can minimize the damage caused by error-recovery by always putting properties before nested rules. In the preceding example, the font-size declaration would be honored by legacy user-agents, even while the rest of the rule is ignored.

3 The & Selector

The selectors inside of a nesting block are implicitly relative to the results of the parent rule’s selector, effectively joined with the parent’s selector by a descendant combinator.

This isn’t always what is intended. One might wish to simply specialize the element selected by the parent, applying some properties to every div but additional rules just to div with a particular class; one might wish to select the sibling of an element; one might wish to specialize the already-selected elements by filtering them based on ancestors.

All of these and more can be accomplished by explicitly indicating in the nested selector where the elements matched by the parent rule’s selector should go. To this end, the & selector, or nesting selector, is introduced.

Within a nesting block, the & selector matches the elements matched by the parent rule’s selector. Outside of a nesting block, the & selector matches nothing.

Introducing the & character will cause issues with CSS embedded directly in XML, as it’s the first character used in CSS syntax that either requires escaping or using CDATA. Do we need to change this?

The following example using Nesting:
  div {{
    .keyword {color: red;}
    &:hover {background-color: rgb(200, 255, 255);}
    section > & {border: 2px solid gray;}
  }}

...produces the same results as the following CSS:

  div .keyword {color:red;}
  div:hover {background-color: rgb(200, 255, 255);}
  section > div {border: 2px solid gray;}

Note: The nesting selector does not change any of the usual rules about compound selectors, such as the requirement that type selectors come first. For example, “&div” is invalid; one must instead write “div&

4 Grammar modifications

This specification alters the CSS Core Grammar and the Selectors grammar.

If this spec is accepted, I’ll just modify Syntax directly to accommodate Nesting.

None of this chapter is correct now that I’m using nesting blocks as the disambiguator.

4.1 Core CSS Grammar

...

4.2 Selectors 4 Grammar

...

4.3 Selectors 4 Lexical Scanner

...

5 CSS Object Model Modifications

The following attribute is required to be added to the CSSStyleRule object defined in Section 6.4.3 of [CSSOM]:

partial interface CSSStyleRule {
    readonly attribute DOMString expandedSelectorText;
    readonly attribute CSSRuleList cssRules;
  };

The cssRules attribute must return a CSSRuleList object for the list of CSS rules specified within the style rule.

The expandedSelectorText attribute must return a DOMString object containing the result of replacing the & selector in the selectorText attribute with the expandedSelectorText attribute from the parent rule. If the rule has no parent rule, the expandedSelectorText attribute must return a DOMString object containing the same text as the selectorText attribute.

The OM here is meant to reflect the OM for @media rules. In particular, the lack of a link from a rule to its parent matches rules nested in @media. Should we add a parentRule property to both of these?

References

Normative References

[CSS21]
Bert Bos; et al. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification. 7 June 2011. W3C Recommendation. URL: http://www.w3.org/TR/2011/REC-CSS2-20110607
[CSSOM]
Anne van Kesteren. CSSOM. 12 July 2011. W3C Working Draft. (Work in progress.) URL: http://www.w3.org/TR/2011/WD-cssom-20110712/

Informative References

[CSS3COLOR]
Tantek Çelik; Chris Lilley; L. David Baron. CSS Color Module Level 3. 7 June 2011. W3C Recommendation. URL: http://www.w3.org/TR/2011/REC-css3-color-20110607
[SELECTORS4]
Elika J. Etemad; Tab Atkins Jr.. Selectors Level 4. 2 May 2013. W3C Working Draft. (Work in progress.) URL: http://www.w3.org/TR/2013/WD-selectors4-20130502/

Index

Property index

No properties defined.

Issues Index

Introducing the & character will cause issues with CSS embedded directly in XML, as it’s the first character used in CSS syntax that either requires escaping or using CDATA. Do we need to change this?
If this spec is accepted, I’ll just modify Syntax directly to accommodate Nesting.
None of this chapter is correct now that I’m using nesting blocks as the disambiguator.
The OM here is meant to reflect the OM for @media rules. In particular, the lack of a link from a rule to its parent matches rules nested in @media. Should we add a parentRule property to both of these?