Tab Completion

I'm Tab Atkins Jr, and I wear many hats. I work for Google on the Chrome browser as a Web Standards Hacker. I'm also a member of the CSS Working Group, and am either a member or contributor to several other working groups in the W3C. You can contact me here.
Listing of All Posts

CSS Mixins

Last updated:

[[As usual, this is a personal draft, and carries no endorsement or acceptance from the CSSWG.]]

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.

(a limited set of Markdown is supported)

#1 - Tom Potts:

Reading the use of the mixin 'bar' above, I must say it doesn't look readable to me at all. When using it like @mixin bar(red, 5px); how do I know which one of the parameters should be a colour, and which a size?

I was about to suggest named arguments to the mixin, but, having just come from the CSS Variables page, it occurs to me that you could use them instead. For example, for the bar example the declaration becomes

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

and the use is

selector {
    var-one: red;
    var-two: 5px;
    @mixin bar;
}

which, given that the mixin will act as if you'd just pasted it in, should work. If you forget to declare one or two then the var() call fails. Also, since the mixin creator can use the var(name, default) form, you get optional arguments in your mixins for free.

The only downside to this I can see is that the actual declaration and use of the mixins don't make explicit the variables/custom properties required, but of course this can be ameliorated with well-commented code. :o)

Reply?

(a limited set of Markdown is supported)

Re #1: Making mixins use the concept of variables for their argument is definitely the plan when I revisit this. I don't really like the totally-implicit use of plain variables to call into mixins, though. For one thing, it makes it quite difficult to use multiple mixins on the same element, unless you take care to uniquify the variable names, such as by including the name of the mixin in them. That's just plain cumbersome at that point, though.

Reply?

(a limited set of Markdown is supported)