W3C home > Mailing lists > Public > www-style@w3.org > September 2013

RE: [css-variables] RE: CSS Variables in Last Call

From: François REMY <francois.remy.dev@outlook.com>
Date: Mon, 9 Sep 2013 01:01:48 +0200
Message-ID: <DUB120-W11959A352028C3E4C870CFA53E0@phx.gbl>
To: Tab Atkins Jr. <jackalmage@gmail.com>
CC: Philippe Le Hegaret <plh@w3.org>, www-style list <www-style@w3.org>
> Similarly, can you elaborate on what you think is wrong with using
> custom properties for [some use-cases]

Firstly, let's consider the case of pure variables:




[1] Token-stream nature of variables

Right now, variables can be use only a pure token stream, and it's impossible to perform any useful operation with them. Let's start with a simple example. Let's imagine that you store your assets on some CDN and want to be able to switch between CDNs if needed, you're out of luck with CSS Variables.

SASS example:

    $assets: 'http://cdn.com/fremycompany/assets';
    $theme: 'blue';
   
    #main {
        background: url("#{$assets}/#{theme}/main-bg.jpg")
    }

Now of course the issue with SASS is that you cannot override the theme on some subsection of your DOM since it's a preprocessor only thing, but it's quite powerful.





[2] Expressions using variables

CSS supports a native computation system, via calc(). However, it gets quickly terribly verbose when you've to use variables with it. For this example, I will use variables to compute the width of an object in a grid whose width must cover the gaps it spans.

SASS example:

     @mixin col-item
     (
        
         $COL_WIDTH  : 280px,
         $COL_GAP    : 20px,
         $SPAN       : 1
        
     )
     {

         width: ($SPAN * $COL_WIDTH) + (($SPAN - 1) * $COL_GAP);
        
     }

     #some-element {
         @include col-item($SPAN: 3);
     }

CSS Variables example:

     .col-item {
         
          var-col-width  : 280px;
          var-col-gap    : 20px;
          var-col-span   : 1;
         
          width: calc((var(col-span) * var(col-width)) + ((var(col-span) - 1) * var(col-gap)));
         
     }

     #some-element {
         
          var-col-span: 3;
         
     }

For what it's worth, the "my-dead" syntax worked like this:

    .col-item {
         
          my-col-width  : 280px;
          my-col-gap    : 20px;
          my-col-span   : 1;
         
          width: get((my-col-span * my-col-width) + ((my-col-span - 1) * my-col-gap));
         
     }

     #some-element {
         
          var-col-span: 3;
         
     }

For what it's worth, I'm almost tempted to say that we don't need "get" at all and could reuse calc now I've written this... Anyway, this is another story.




[3] Conditions on variables

For now, the only condition you can use for variables is "Is var-xyz invalid?" in which case you can provide a default. I'll continue over that later, but anyway, this is the only conditional statement you can write.

While the original Custom Properties fork only featured the || operator, for fallbacks, I'm seriously envisioning a full expression language support:

    #some-element {
        width: get(my-popup-display==val(minimized) ? ... : ...)
        height: get(my-popup-display==val(minimized) ? ... : ...)
        position: get(my-popup-display==val(minimized) ? ... : ...)
        ...
    }




[4] Buggy fallback

You can't provide safe computation-linked variables because of the cycle breaking algorithm.

    .rectangle {
        var-rectangle-width   : calc(var(rectangle-area) / var(rectangle-height,1))
        var-rectangle-height  : calc(var(rectangle-area) / var(rectangle-width,1))
        var-rectangle-area    : calc(var(rectangle-width,0) * var(rectangle-height,0))
    }

    #some-element.rectangle {
        var-rectangle-width   : 300;
        var-rectangle-area    : 9000;
    }

Because all of them would resolve to invalid. Using the algorithm defined in the Custom Properties fork would correctly resolve all variables to 0 in the first case, and to (300, 30, 9000) in the second.




[5] Buggy fallback syntax

The current syntax is still crazy when it comes to default value. I've said it ten times already but

    #some-element {
        background-size: var(content-bg-sizes, cover, cover, cover)
    }

doesn't make any sense while

    #some-element {
        background-size: get(my-content-bg-sizes || cover, cover, cover)
    }

does.




[6] Extreme difficulty to do progressive enhancement

This one isn't new either, but you can't do:

    #some-element {
         background: var(compat-bg, url(...));
         background: var(hyperconic-bg, hyperconic-gradient(...));
    }

And you have to do:

    #some-element {
        background: var(compat-bg, url(...));
    }
    @supports(background: hyperconic-gradient(...)) {
        #some-element {
            background: var(hyperconic-bg, hyperconic-gradient(...));
        }
    }

This is superlong to write, and therefore prone to mistakes.




Okay, that's all for the basic variable use case. Let's move the to polyfill use case:




[1] Hard to detect and hard to control

Firstly, as you noted, it's undetectable that some custom property apply on an element. But, worse, all properties inherit by default. This means that it's not just sufficient to get watch all selectors that may apply the property, you also have to make sure the current value isn't inherited by a parent.

A Style Mutation Observer like you described would help in this case, but wouldn't solve the inherit problem. Of course, you could also introduce a "* { var-x: var(x) }" rule in your stylesheet but that's not that cool.




[2] Impossible to validate, and cascade properly

The value of a custom property cannot be typechecked properly. As a result, if you end up with some non-sensical value, you've no way to recover the next value in the cascade order. If you cascade yourself, you can easily do it. Not being able to perform this prevent you from making good polyfills.

    #some-element {
        my-background: green;
        my-background: non-sense;
    }




[3] Non-pure values

Since you get the value after some computation work, you may have issues getting things working properly. An example: "inherit" and "initial" seem to have been defined as working normally on a variable. However, setting "initial" on a variable makes it invalid, which is not distinguishable from the property not being defined on the element.

If you want to polyfill some other property, initial has a meaning you want to preserve. Ditto for inherit, fwiw, and in the case of an error in the variables stuff (cycles & co).




Finally, let's discuss the web components case. These are kinda weaker since I don't have enough experience with web components and they're not going to become mainstream for a while, but anyway:




[1] Variables Omniscience

Since variables inherit, if you have web components inside another web component, you've to make sure you reset all the variables the web components use to their initial state to prevent directive from the parent webcomponents to affect the second web compoments.

That means you have to know in advance all properties that can apply to a web components and reset them. This is prone to mistakes as this set of recognized properties will vary over time.

The issue here is that you can't limit the scope of your variable automatic inheritance.




[2] Variables Context

If you have variables that do not inherit (aka *{var-x:var(x)}) then you cannot use the value as defined on the host of the current web components. The Custom Properties fork featured a way to reference properties on SOME element, where SOME could be defined.

The current system force the introduction of a new function each time we need a new source of information for the references.




[3] Nested Variables

I've discussed this before*, and I think we'll want nested variables in the future. Not sure about the syntax already, but anyway here's what I wrote before:

    // css facility for var-template
    * { 
          var-template: get(invalid);
          decorator: get(var-template.decorator || none); 
    }
    
    // styling of the article
    article {
          var-template: {
                decorator: url('/templates/#paged-article');
                first-page: { 
                      decorator: url('/templates/#article-first-page');
                      image-height: 25vh;
                };
                page: {
                      url('/templates/#article-page');
                      display-quotes: true;
                      display-images: true;
                };
          };
    }
    
    // inside the {paged-article} template
    .page:first-of-type { 
          var-template: get( host var-template.first-page || host var-template.page );
    }
    
    .page { 
          var-template: get(host var-template.page);
    }

But this issue is not related to the current draft as we can extend it to support this use case later without changing anything of it, it seems.

  *PS: I discussed this in one of my even weaker mail: http://lists.w3.org/Archives/Public/www-style/2013Apr/0348.html 




Okay, I'm not sure it covers everything I've in mind, but that's a good beginning already! I was fearing it could end up longer but all in all this is quite acceptable stil.

Best regards,
Francois 		 	   		  
Received on Sunday, 8 September 2013 23:02:16 UTC

This archive was generated by hypermail 2.3.1 : Sunday, 8 September 2013 23:02:16 UTC