Search code examples
csspreprocessortranspiler

Compile non-root CSS custom property


Are there any tools to compile CSS custom properties declared at not :root rule? I want following code with custom properties

.dark {
  --bg-color: black;
  --fg-color: white;
}

.light {
  --bg-color: white;
  --fg-color: black;
}

.foo {
  background: var(--bg-color);
  display: block;
}

.bar {
  color: var(--fg-color);
  display: inline;
}

be compiled to their non-custom-prop equivalents like that

.light .foo, .light.foo {
  background: white;
}

.dark .foo, .dark.foo {
  background: black;
}

.light .bar, .light.bar {
  color: black;
}

.dark .bar, .dark.bar {
  color: white;
}

.foo {
  display: block;
}

.bar {
  display: inline;
}

The goal is to

  • switch color schemes by switching dark/light class on root DOM element
  • use valid css syntax (no sass less)
  • keep rules code compact

Solution

  • Ideal solution. Your example is valid CSS and can be used in many browsers (not in IE, Edge (but is in development) and Opera Mini as of writing this answer, 2017-03-27, other major browsers are fine).

    Suboptimal solution. Some CSS can be transpiled to achieve better browser support. The solution I found does not support variables on non-:root elements, however. There are also other objections against transpiling of 'future' CSS into 'current' CSS. To the best of my knowledge, you will have to implement your own transpiler (or postcss plugin) if you want to transpile custom properties not on the :root element, but be warned that that is hard in general. Now you don't need the general part, so it is possible. Just does, to the best of my knowledge, not exist yet.

    Preprocessing solution. Of course, you don't need a general implementation of custom properties. You have different themes that have their own values for the same set of properties and that's it. Thus, a separate stylesheet can be created as a preprocessing step using any CSS preprocessor.

    Now you say the following,

    use valid css syntax (no sass less)

    but I am going to show this anyway, because I believe that it is a valid solution to your problem. It is definitely the only one I know that actually works if you want to/need to support IE, Edge and/or older versions of other major browsers (Firefox < 31, Chrome < 49, Safari < 9.1, Opera < 36)

    You could do this using SASS for example, to do the transpiling on the server side.

    // define styles, use variables throughout them
    // your entire style definition goes into this mixin
    @mixin myStyles($fg-color, $bg-color) {
      .foo {
        display: block;
        background: $bg-color;
      }
    
      .bar {
        display: inline;
        color: $fg-color;
      }
    }
    
    // define themes, that set variables for the above styles
    // use named arguments for clarity
    .dark {
      @include myStyles(
        $fg-color: white,
        $bg-color: black
      );
    }
    
    .light {
      @include myStyles(
        $fg-color: black,
        $bg-color: white
      );
    }
    

    This compiles to the following.

    .dark .foo {
      display: block;
      background: black;
    }
    .dark .bar {
      display: inline;
      color: white;
    }
    
    .light .foo {
      display: block;
      background: white;
    }
    .light .bar {
      display: inline;
      color: black;
    }
    

    This is not exactly what you want to obtain, but very close. Realistically, I think this is the closest you will get to obtaining your desired output. I know you want to

    keep rules code compact

    but what you are saying there (I think) is that you want to split out custom properties from their rules to save on number of rules, which is not something any preprocessor I know supports.

    You can organize your source SASS in separate files to keep an overview easily. You can even set up a build system that generates a separate stylesheet for every theme you have. It is then possible to have your users select an alternative stylesheet. Browsers have some support for this, but switching using JavaScript is also definitely possible in the latter case. Simply set all stylesheets to be disabled except for the selected one. Here is an example.