CSS Variables

A few weeks ago I gave a talk about some experimental things some of
our teams at Chrome are working on.  (You can see the subsequent
blogpost at <http://www.xanthir.com/blog/b49w0>.)  I obviously want to
bring all of them to the group soon, but I want to start on Variables,
being the simplest and most-requested feature.

We've been arguing about CSS Variables for a long time, but could
never agree on how it was supposed to work.  This sucks for authors,
particularly as the rise of preprocessing frameworks like SASS have
shown just how popular and useful variables are in designing and
theming webpages and webapps, both large and small.  I and some other
Chrome engineers have put together a proposal for variables that is
very similar in nature to what glazou proposed in
<http://disruptive-innovations.com/zoo/cssvariables/> (the syntax is a
bit different, but that's mostly a bikeshedding detail).

Here are a few

1. Variables are mutable.  You can have multiple declarations of the
same variable in a stylesheet, and the last one wins, per normal CSS
tradition.  You can change variables using the CSSOM, and the updated
value will flow through all the uses of the var.

This is very necessary to solve, for example, the use-case of having
several similar elements scattered across a page all change their
backgrounds together.  Mutable variables let you just set up a
variable for the shared color, use it in their declaration, and then
tweak the variable value in javascript.  This is enormously easier
than either grovelling through stylesheets and somehow finding the
correct rule to alter, or iterating through all the elements and
adjusting their inline style.

2. Variables are document-global.  Once defined, they can be used in
any stylesheet on the page, preceding or following, @import'd or just
linked in separately.

Last time variables were discussed, this was a significant sticking
point.  The major objection was conflicts between different usages;
for example, a page-specific style defining a variable that happened
to conflict with a variable defined by the site template's style.  To
help solve this, proposals of varying complexity were introduced to
limit the scope of variables and allow them to 'bleed out' (or in) in
controlled ways.

We think that this solution is best addressed at a higher level.
Using the global namespace is definitely easier for authors, and
conflicts in this space will simply get worse as we introduce more
subspaces (variables, mixins, specialized ones like font-faces and
counter-styles).  This should be addressed by a general mechanism -
we're going to be experimenting with a Module system to help out here
(outlined in my blog post), so we'll see how it goes.

3. Variables respond in the obvious way to dynamic changes.  Adding a
var (whether through the CSSOM variable map, adding a new @var rule
directly to a stylesheet via the CSSOM, or adding a new stylesheet to
the document) enables use of that var everywhere.  If a var is used
before it's defined, adding a definition later will make all uses
suddenly work.  Removing vars with any method will either fall back to
a previous definition for that name, if one exists, or just make all
usage of that variable invalid.

The syntax isn't complex.  What we're going with so far is this:

@var $foo red;
bar {
  color: $foo;

The @var rule declares a single variable, taking a name and then
arbitrary CSS as a value.  The var name must start with a $ character,
so we can use $ as an unambiguous indicator in the content that a
variable is being used.

CSSOM access is done through the global vars map, like so:

console.log(css.vars.foo); // logs "red", given the above
css.vars.foo = "blue"; // Everywhere $foo is used is now "blue".

This example supposes that there is a global css object defined on
document (also on window, forwarding to document.css) that gives
access to document-global CSS things, like vars.  This way you don't
need to know which stylesheet a var comes from to alter it, making
variables even easier to use.  One could imagine that this map is
instead defined on stylesheets, but that's more difficult to work with
for literally no gain.

(Technical detail: if there are multiple @var rules declaring the same
variable, the var map entry points to the current "active" rule.)

(Another technical detail: scoped stylesheets should scope their vars,
and preferably other global namespaces.  A mechanism needs to be
provided to grab the css environment that a particular element sees,
rather than the global css environment, so one can manipulate
variables defined in scoped stylesheets easily.)

I believe this model of interaction is enormously simpler than what
was in glazou's proposal, which would require a script to grovel
through all the rules in all stylesheets to find var rules, and use
verbosely-named functions to alter them.

This model is also easily extensible for when we get the CSSOM
improvements that allow type-based manipulation of values.  To enable
the CSSColor interface on a var, for example, declare it like so:

@var color $foo red;

Or set it in script like so:

css.vars.foo = new CSSColor("red");
// strawman syntax, don't pay any attention

So, thoughts?  I'm willing to write this spec, if the WG accepts it as
a work item.  We're already working on an experimental implementation
regardless, so we can see how it works in practice for people.


Received on Sunday, 6 February 2011 00:35:33 UTC