W3C home > Mailing lists > Public > www-style@w3.org > February 2011

Re: CSS Variables Draft Proposal

From: Linss, Peter <peter.linss@hp.com>
Date: Thu, 10 Feb 2011 02:56:43 +0000
To: Tab Atkins Jr. <jackalmage@gmail.com>, www-style list <www-style@w3.org>
Message-ID: <C9788D84.490C%peter.linss@hp.com>
Notes inline


On 2/9/11 5:22 PM, "Tab Atkins Jr." <jackalmage@gmail.com> wrote:

>Variable Syntax
>The syntax for variables is very simple.  Variables are defined in an
>`@var` rule anywhere in the sheet (though it is recommended that they
>be defined at the top of a sheet for organizational purposes):
>@var $main-color blue;
>The syntax is `@var`, followed by whitespace, followed by the variable
>name (which must start with a $, and then follow the IDENT
>production), followed by whitespace, followed by an arbitrary series
>of CSS tokens, capped by a semicolon at the end.
>(I want to impose stricter requirements on variables values than just
>"a token stream", but it's not immediately clear how to do so.  See
>the Typed Variables section later down, though.)

"A token stream" is really dangerous (or exceptionally powerful, depending
on your point of view). You're definitely going to have to tighten this
up. Consider:

@var $foo bar, baz / 100px     ;

Does that simply get dropped into place macro style? Including the leading
and trailing whitespace?

Do we really want that?

How about:
@var $foo url(;
@var $bar );

p { content: $foo http://example.com/yikes.gif $bar }

>Using variables is very easy too - they can be used anywhere you could
>use a component value:

I can see use cases for having variables in selectors or as properties.
But we probably don't want to go there.

>p {
>  color: $main-color;
>  background: url(foo) $main-color no-repeat;
>  list-style-image: radial-gradient($main-color, $secondary-color);

FWIW, I truly despise this syntax. Yes, it's used in a bunch of
programming languages, that doesn't make it good.

One technical argument against it is that you can't abut a variable
against a trailing identifier or number (unless you add a "eat one
trailing whitespace rule like for escapes). Although depending on how the
"token stream" gets restricted this may not matter.

>Using a variable that hasn't been declared is a syntax error.  (It's
>valid to use a variable that hasn't been declared *yet* - the
>declaration may appear later in the stylesheet, or in another sheet

Don't use the term "syntax error" here. I read this as "invalid and may be
thrown away at parse time", which is clearly not your intent.

Better to define referencing an undefined variable as having a 'null'
value, or more likely a value of 'invalid' which is a special token
meaning that it's there, but will always be invalid. You also need a way
to "unset" a variable via script and/or set it to a null/invalid value.

>Variables and @import, etc.
>Variables exist in the global scope.  @import'ing a stylesheet makes
>any variables contained within it available to all other sheets.
>Similarly, linking in a stylesheet makes any variables contained
>within it available to all other sheets.

A mention of alternate stylesheets as well as disabled stylesheets would
be good here too. I presume the variables should be out of scope if the
stylesheet isn't applied.

>Scoped stylesheets (those created with a `<style scoped>` element in
>HTML) have their own nested global scope.  Variables created or
>imported within a scoped stylesheet are only available within the
>scoped stylesheet; variables created in the outer global scope are
>still available in a scoped stylesheet.
>(It's expected that we'll solve name collisions through some kind of
>module system giving us lightweight namespacing.)
>Variables declared in an @media block are only available if the media
>declaration is true.
>Multiple Variable Declarations
>If the same variable name is declared in multiple @var rules, the last
>valid declaration wins.  For this purpose, UA-defined variables come
>before all author-defined rules, which come before all user-defined
>rules.  Within each category, the ordering is document order.  (This
>is intentionally identical to normal CSS precedence rules, except that
>there's no tag/class/id/important bits.)
>Changing the Variables in a Document
>If new variables are added to the document, such as through
>dynamically linking in a new stylesheet, they are added to the set of
>variables, possibly changing the value of existing variables or adding
>new variables.  If a new variable is introduced, any declarations that
>referenced that variable's name, and were thus invalid, are now valid.
> Similarly, removing stylesheets might remove @var rules from the
>document, which can change the value of variables used in a stylesheet
>or, if the removed @var was the only definition of a particular
>variable name, make declarations which reference that variable name
>Variables Referring to Variables
>It's valid to create a variable that depends on the value of another
>variable, like so:
>@var $foo red;
>@var $bar linear-gradient(transparent, $foo);
>When producing the set of variables, browsers must track which
>variables depend on which.  If a dependency cycle is detected, all the
>declarations that contributed to the cycle are invalid.  For example,
>in this code:
>@var $foo red;
>@var $bar $foo;
>@var $foo $bar;
>The latter two declarations form a dependency cycle, and so are
>invalid.  A single variable named `$foo` is created, with the value
>`red`.  If you then delete the third rule, the second is no longer
>part of a cycle, so the `$bar` variable is valid and contains the
>value `red` as well.

Invalid at parse time or at run time? If variables are changed via script
you should detect and deal with (or detect the absence of) loops after
each change...

>Changes to CSS Grammar
>To be completed with boring details.
>Object Model
>Same as <http://disruptive-innovations.com/zoo/cssvariables/> for the
>low-level, stylesheet-iterating form.
>For use by normal authors, I'll be proposing a new global object on
>`document` named `css`.  `css.vars` will contain a map of vars.  This
>map will reflect all valid vars in the global namespace, with the key
>being the variable's name, suitably transformed (see below), and the
>value being the current value of that variable.
>Changing the value of a map entry changes the underlying value in the
>stylesheet for the declaration being used to produce that value.  That
>is, given a stylesheet like this:
>@var $foo red;
>@var $foo blue;
>Executing a command like `css.vars.foo = "yellow";` and then looking
>at the stylesheet again would produce this:
>@var $foo red;
>@var $foo yellow;
>Deleting a map entry either deletes the current declaration producing
>that value, or deletes all declarations defining that variable.  The
>former is more symmetric with the underlying behavior of the "change"
>action, but the latter is more symmetric with the *apparent* behavior,
>as the complexity of multiple declarations is hidden away.

There should also be a way to access all declarations, even if overridden.
Insert, remove, reorder, etc. Or (probably better) you can't change this
list, only the values of the variables in it. And if you do, you're really
changing the @var object in whichever stylesheet it came from.

>To add a new map entry, we first define `css.stylesheet`, which
>implements the `StyleSheet` interface.  This stylesheet is treated as
>an author-level sheet placed after all other author-level sheets.
>Creating a new map entry creates a corresponding @var rule in this
>stylesheet.  (Should this exist in `document.stylesheets`?  If so,

As I think about it, adding and removing @var rules should probably only
exist in the document.stylesheet interface.

>To map a variable name to a key in the variables map, follow these steps:
>1. Remove the $ prefix from the name.
>2. Lowercase the remaining letters.
>3. Uppercase any letters that immediately follow a hyphen.
>4. Remove any hyphens.
>Given this algorithm, a variable like `$main-color` will be accessed
>as `css.vars.mainColor`.

And if I had a variable named $mainColor ? You're effectively making them

>Typed Variables
>There are many new improvements planned to the CSSOM to allow authors
>to edit CSS values in more intuitive ways, such as exposing `.red` on
>colors to allow direct manipulation of the red component.  When
>editting a property value, the property gives context to the values,
>so you can assign the correct interfaces to the component values based
>on their implied type.  We'd like to expose the same abilities on
>variables, but they lack the context that property values have, so
>it's difficult to tell what interfaces to offer.
>There are two approaches we've considered for dealing with this -
>optional typing, late typing, and "omni-typing".
>(Before continuing, I'll note that the obvious answer of "inferred
>typing" appears to be a non-starter.  There are many ambiguous cases
>where you can't tell ahead of time what type a value would be.)
>(The definition of a 'type' is intentionally somewhat loose right now.
> At minimum, every primitive value is a type, as is every property.
>We may also want some complex component types, like <position>.)
>### Optional Typing ###
>Optional typing is just an amendment to the declaration syntax that
>allows the author to specify the variable's type directly.  It would
>look like this:
>@var color $foo red;
>Untyped variables would just expose the legacy string-based CSSOM
>interface, while typed ones would expose the appropriate new
>interfaces based on the type.
>### Late Typing ###
>The previous suggestion seems to put the typing in the wrong place.
>Typing doesn't help the CSS developer in any way, as CSS can figure
>out types as necessary all by itself.  It's only useful for the JS
>developer.  Perhaps, then, the burden of typing should be on the JS
>In this approach, variables are untyped by default, but JS authors can
>"cast" them into particular types to expose the appropriate
>css.vars.foo.asColor.red = 50;
>var x = css.vars.bar.asLength;
>The object returned by a cast has all the same functionality as the
>original variable object, just with extra interfaces on it.  If a
>variable's value would be invalid when interpreted as that type (for
>example, if the variable's value was "5px" and you called `.asColor`
>on it), an error is thrown.
>### Omni-typing ###
>The previous suggestion is somewhat cumbersome, as it requires the dev
>to type the variable every time they want to use an interface, or
>store a reference to an already-typed object.  The UA still has to
>handle the possibility that the dev tries to cast it to a bad type.
>We could instead just expose all interfaces, and throw an error if the
>dev tries to use an interface from a type that is incompatible with
>the variable's value.
>css.vars.foo = "red";
>css.vars.foo.blue = 255; // Good
>print(css.vars.foo); // "rgb(255,0,255)"
>css.vars.foo.em = 6; // error, the above value can't be interpreted as a
>This would only work if the OM interfaces were carefully designed in
>such a way that there is never ambiguity, or the ambiguity is
>harmless.  For example, a variable containing "red" could expose a .l
>property, which could potentially correspond to it being interpreted
>as a background-color list, a font-family list, or some other values,
>but the ambiguity doesn't affect anything here.
>Possibly this could be mixed with late typing, so that we auto-expose
>interfaces associated with primitive types, but require casting for
>property-types and other complex types so the potential for ambiguity
>is minimized.

And if the value of the variable is a list of values? (like for a
Received on Thursday, 10 February 2011 02:57:36 UTC

This archive was generated by hypermail 2.4.0 : Friday, 25 March 2022 10:07:56 UTC