CSS Mixins proposal

I recently gave a talk and blogged about a few experimental CSS
features that Chrome is playing with.  I've already talked about one
of them here on the list, CSS Variables, which got a relatively
positive reception.  Another one, CSS Mixins, has been moving faster
in implementation than I anticipated, so I haven't yet had a chance to
talk about it despite it starting to land in Webkit patches.  So, I'd
like to start talking about this feature and whether the CSSWG is
interested in pursuing it.

A mixin is a block of rules that can be "mixed into" other declaration
blocks.  This enables you to create chunks of reusable CSS, avoiding
duplication of code when you have bunches of otherwise-dissimilar
elements that happen to all use a particular thematic grouping of
rules.  Mixins are used in, for example, SASS
<http://sass-lang.com/tutorial.html#mixins>.

(Why can't you just add a class to these elements and put the
"reusable" CSS in a ruleset targetting that class?  While this
strategy does the job, it requires you to alter your HTML and sprinkle
otherwise-meaningless classes around your page.  If you want to change
what elements receive the special CSS, you have to change your
document, which is an anti-pattern that CSS and Selectors were
supposed to solve.  Basically, implementing mixins via classes is
roughly equivalent to putting "redText" classes in your HTML - it
leaks presentational information into your document, rather than
keeping it in the CSS where it belongs.)

The syntax that we're experimenting with for Mixins is fairly simple -
it involves two new @-rules, one at the top-level and one that lives
in declaration blocks.

The first one declares the mixin, which we're calling a "trait" (name
very much temporary).  It looks like this:

@trait foo {
  prop: val;
  prop: val;
}

The syntax is "@trait", followed by the name of the trait, followed by
a declaration block.

Traits can also take arguments, which are treated like local variables
within the body of the mixin:

@trait bar($one, $two) {
  prop: $one;
  prop: $two;
}

(What's the scope of these local variables: dynamic or lexical?  The
former would allow arguments to percolate down into nested mixins.
The latter is a lot simpler to read and reason about.  I like lexical
scope much more.)

(These local variables presumably shadow any global variables with the
same name.  Do we need a way to access the global var?  Assuming
lexical scope, the arguments shadow a very localized area, and the
argument names are under the control of the same person who writes the
CSS in the decl block presumably, so they can just rename if
necessary.  The var() notation for variables may make this easier by
allowing a local() function for accessing arguments.)

(Probably we want to allow default values for the arguments as well.
This has similar ambiguity problems as the @mixin rule, and should be
solveable in the same way.)

Within a declaration block, you can invoke a mixin by using @mixin, like so:

selector {
  prop: val;
  @mixin foo;
  @mixin bar(red, 5px);
}

The effect of this is as if you had simply textually substituted the
contents of the trait's declaration block at the point of the @mixin
rule.  Rule overrides happen based on the "virtual position", etc.

(There is an ambiguity with the arguments here - variables allow
comma-separated values, which would conflict with comma-separated
arguments.  SASS gets around this by forcing you to enclose
comma-separated values in parentheses.  Is this reasonable?)

On the CSSOM side, Traits would be a new type of CSSRule.  I'm not
*entirely* sure how the @mixin would be represented, because we
haven't yet had to worry about @-rules inside of declaration blocks.
I suspect it might involve giving CSSStyleRule a cssRules property,
like CSSMediaRule, and listing the @-rules there.

Thoughts?

~TJ

Received on Monday, 21 March 2011 19:56:34 UTC