Search code examples
csssvgcss-variables

CSS Variables default to SVG fill if invalid


I have an SVG with a number of rects that change fill colour as they move down the SVG (in a sort of gradient look). Users are able to choose a solid fill colour to 'theme' their experience. This colour replaces the 'gradient' color of the rects. I'm doing this using CSS variables.

However, I want to default back to the fill colour defined in the SVG if they don't choose a theme colour. In this case the CSS variable is set to '' making it invalid. For other elements I'm using a default that the element falls back to. I can't do this with the SVG rects as they're all different. I tried removing the default but I believe this sets the fill to it's initial CSS value, which is transparent.

If I have the following rect: <rect id="rect" fill="#000000" x="0" y="0" width="200" height="50" rx="6"></rect> and the following CSS: rect { fill: var(--preview-primary); } I'd expect it to be black when --preview-primary is invalid, but it's transparent.

Is there a way I can do this? Thanks.


Solution

  • No, there is no way to fallback to the fill attribute.

    Having a valid fill CSS rule will take precedence over the fill attribute. For CSSOM, var(--anything) is always a valid rule. If ever the computation of the var() function is invalid, then it will fallback to the default value. And the default value for fill is black:

    #f {
      fill: var(--foobar);
    }
    <svg>
      <rect id="f" x="0" y="0" height="80" width="80" fill="green"/>
    </svg>

    So to workaround this situation, you have a few choices available:

    • If you can't modify your SVG, you can toggle-on user-selected rules only when the value is not "".

    sel.onchange = e => {
      document.documentElement.style
        .setProperty('--selected-color', sel.value);
      // toggle a class so we know we have to handle it
      document.documentElement.classList
        .toggle('user-selected-color', sel.value !== "");
    };
    .user-selected-color rect {
      fill: var(--selected-color);
    }
    select{vertical-align:top}
    <svg height="180" width="180">
      <rect x="0" y="0" height="80" width="80" fill="green"/>
      <rect x="90" y="0" height="80" width="80" fill="blue"/>
      <rect x="0" y="90" height="80" width="80" fill="red"/>
      <rect x="90" y="90" height="80" width="80" fill="black"/>
    </svg>
    
    <select id="sel">
      <option value="">none</option>
      <option>orange</option>
      <option>pink</option>
      <option>violet</option>
      <option>aqua</option>
    </select>

    • If you can, you could set each element's CSS color and then fallback to the CSS value currentColor:

    sel.onchange = e => {
      document.documentElement.style
        .setProperty('--selected-color', sel.value);
    };
    rect {
      fill: var(--selected-color, currentColor);
    }
    select{vertical-align:top}
    <svg height="180" width="180">
      <rect x="0" y="0" height="80" width="80" fill="green" style="color:green"/>
      <rect x="90" y="0" height="80" width="80" fill="blue" style="color:blue"/>
      <rect x="0" y="90" height="80" width="80" fill="red" style="color:red"/>
      <rect x="90" y="90" height="80" width="80" fill="black" style="color:black"/>
    </svg>
    
    <select id="sel">
      <option value="">none</option>
      <option>orange</option>
      <option>pink</option>
      <option>violet</option>
      <option>aqua</option>
    </select>