W3C home > Mailing lists > Public > www-style@w3.org > October 2010

Nesting declaration blocks

From: Tab Atkins Jr. <jackalmage@gmail.com>
Date: Mon, 18 Oct 2010 15:46:00 -0700
Message-ID: <AANLkTin7YqsKLcFH3hYZKJG3dYFxMeR2NV9AaPyvQ1e0@mail.gmail.com>
To: www-style list <www-style@w3.org>
CSS preprocessors like SASS and LESS are getting more and more
attention, because they simply make the authoring experience better in
several ways.  They offer Variables and Mix-ins, for example, which we
already want to move on (and have for some time in the case of
variables, but dropped the ball on).

One particular ability that both of those frameworks have that people
continually ask me for, though, is declaration block nesting.  That
is, the ability to do this:

#header {
  prop: value;
  img {
    prop: value;
  }
  > nav {
    prop: value;
    > ul {
      prop: value;
      > li {
        prop: value;
        > a {
          prop: value;
        }
      }
    }
  }
}

...and have it be equivalent to:

#header {
  prop: value;
}

#header img {
  prop: value;
}

#header > nav {
  prop: value;
}

#header > nav > ul {
  prop: value;
}

#header > nav > ul > li {
  prop: value;
}

#header > nav > ul > li > a {
  prop: value;
}

This is sorta an extreme example, but the ability to nest selectors
does indeed make your code more readable.  I know that it's a
relatively common practice to indent selectors to match the implicit
nesting, like this:

#header {
  prop: value;
}

  #header img {
    prop: value;
  }

  #header > nav {
    prop: value;
  }

    #header > nav > ul {
      prop: value;
    }

      #header > nav > ul > li {
        prop: value;
      }

        #header > nav > ul > li > a {
          prop: value;
        }

Nesting thus has several authoring advantages:

1) It makes it somewhat easier to read the stylesheet, if done well.
You don't have to constantly reparse each selector to ensure that it's
just selecting a descendant of the previous one.

2) It makes the stylesheet as a whole shorter without loss of
readability.  Less typing is good, shorter content is good.

3) It makes editting much easier.  If the page structure changes (say
#header has its id changed), right now you have to change a *lot* of
selectors.  With nesting, you just change one.


Of course, CSS has a big problem with selector nesting like in my
first example - it breaks the core grammar pretty badly.  (It might
also require arbitrary lookahead, but I'm not sure of that.)

Discussing this around the team recently, though, we hit on a possible
solution - @-rules.  I believe that nesting @-rules in a decl block is
officially against the core grammar right now, but we discussed this
back in the April ftf and kinda-sorta agreed to change this, with the
recommendation that @-rules in decl blocks be put at the end, where it
would break in a controllable way.

Using @-rules, you could introduce nesting with only the minimal
grammar breaking we talked about.  It would look something like this:

#header {
  prop: value;
  @nest(img) {
    prop: value;
  }
  @nest(> nav) {
    prop: value;
    @nest(> ul) {
      prop: value;
      @nest(> li) {
        prop: value;
        @nest(> a) {
          prop: value;
        }
      }
    }
  }
}

Another similar case is when you don't want to select
descendants/siblings, but just specialize the current selector.  For
example, take this code:

body > article.post > form input[type=checkbox] {
  prop: value;
}

body > article.post > form input[type=checkbox]:checked {
  prop: value;
}

The only difference between the two selected is the :checked
specializing the last part, but you have to repeat the entire long
selector, and change both of them if the structure changes.  This
could potentially be changed to something like:

body > article.post > form input[type=checkbox] {
  prop: value;
  @this(:checked) {
    prop: value;
  }
}

Another large case for this is doing ::before/::after or other
pseudoelements of an element that you are already styling.  Same
thing:

my > long > selector > string {
  prop: value;
}

my > long > selector > string::before {
  prop: value;
}

is replaced by:

my > long > selector > string {
  prop: value;
  @this(::before) {
    prop: value;
  }
}

I find the latter much more readable, and definitely more maintainable.

Thoughts?

~TJ
Received on Monday, 18 October 2010 22:47:11 GMT

This archive was generated by hypermail 2.3.1 : Tuesday, 26 March 2013 17:20:33 GMT