We've got an internal effort to try and make webapps and large
webpages easier to write and maintain. As part of this we want to
develop Variables and Mixins, but another idea we had was that of
Hierarchy, or Selector Nesting. The idea here is to enable selectors
to be nested within each other, so that instead of writing:
foo {
prop: val;
prop: val;
}
foo bar {
prop: val;
}
you can write:
foo {
prop: val;
prop: val;
& bar {
prop: val;
}
}
The advantage of Hierarchies is that you can keep related styles in the same
place, rather than spread across your document, and you can hence more clearly
see how various style rules relate to each other. This helps reduce cognitive
load on the reader and improves maintainability by taking advantage of CSS's {}
scoping.
SASS provides an example of this idea in action
<http://sass-lang.com/tutorial.html#nesting>.
Hierarchies and Mixins work really well together to enable reusable,
hierarchical chunks of CSS to be defined then included in the appropriate
place. Using this approach to define styled widgets avoids many of the issues
that traditionally have to be solved via the specificity rules. (See
footnote for an example)
We've considered a few different syntax options, but have settled on
recommending the use of a single '&' character to indicate hierarchical scope.
This allows both space-separated and directly adjacent nesting to be supported:
foo {
prop: val;
& bar {
prop: val;
}
&:hover {
prop: val;
}
}
One issue that we've not yet completely resolved is the interaction between
Hierarchies and selector lists. For example:
foo, bar {
& far {
prop: val;
}
}
Currently we are leaning towards this being supported and equivalent to:
foo far {
prop: val;
}
bar far {
prop: val;
}
On the other hand, we are leaning towards only allowing selector lists in
nested selectors via repeated insertion of the '&' character:
foo {
& bar, & far, &:hover {
prop: val;
}
}
With respect to the CSSOM, nested selectors would be exposed as .cssRules
on the CSSStyleRule object, identical to how CSSMediaRule works already.
This feature, as we currently have it imagined, is incompatible with the current
Core Syntax. However, it degrades well in existing browsers if you put the
nested rules below all the properties in a rule. There is another,
slightly more
verbose, syntax option using @-rules that would only require the same
changes that
Mixins do, where we allow @-rules inside of rules.
FOOTNOTE: Mixins and Hierarchies
--------------------------------
Using Mixins and Hierarchies together, set of nested behaviours can be defined
hierarchically, then included where necessary using @mixin. Keeping the
behaviours defined within a mixin reduces pollution of the CSS namespace and
hence reduces the need to resort to specificity rules. However, the use of a
@mixin allows these rules to be used in the appropriate places:
@trait hover-behaviour {
foo {
prop: val;
prop: val;
& bar {
prop: val;
&:hover {
prop: val;
prop: val;
}
&:active {
prop: val;
prop: val;
}
}
}
}
far {
@mixin hover-behaviour;
}
FOOTNOTE: Extended Example
--------------------------
Hierarchy looks like unnecessary line-noise when you see only small
examples. It really shines when you see it in larger examples, like
you'd get in actual applications. Here's an example from a recent
internal presentation.
This is an actual chunk of CSS taken from the code for a slideshow we
recently wrote:
.slide.image {
background-repeat: no-repeat;
background-size: cover;
background-position: top left;
}
.slide.image .credit { /*...*/ }
.slide.image .counter { /*...*/ }
.slide.image section { /*...*/ }
.slide.image section h1,
.slide.image section h2 { /*...*/ }
.slide.image section.bottomLeft { /*...*/ }
.slide.image section.bottomRight { /*...*/ }
.slide.image section.topLeft { /*...*/ }
.slide.image section.topRight { /*...*/ }
.slide.image section.topWide { /*...*/ }
.slide.image section.bottomWide { /*...*/ }
The duplication is clear and somewhat jarring. Here's the same thing,
rewritten to use Hierarchy:
.slide.image {
background-repeat: no-repeat;
background-size: cover;
background-position: top left;
& .credit { /*...*/ }
& .counter { /*...*/ }
& section {
/*...*/
& h1, & h2 { /*...*/ }
&.bottomLeft { /*...*/ }
&.bottomRight { /*...*/ }
&.topLeft { /*...*/ }
&.topRight { /*...*/ }
&.topWide { /*...*/ }
&.bottomWide { /*...*/ }
}
}
~TJ