Autoprefixing, with CSS variables!

Recently, when I was making the minisite for markapp.io, I realized a neat trick one can do with CSS variables, precisely due to their dynamic nature. Let’s say you want to use a property that has multiple versions: an unprefixed one and one or more prefixed ones. In this example we are going to use clip-path, which currently needs both an unprefixed version and a -webkit- prefixed one, however the technique works for any property and any number of prefixes or different property names, as long as the value is the same across all variations of the property name.

The first part is to define a --clip-path property on every element with a value of initial. This prevents the property from being inherited every time it’s used, and since the * has zero specificity, any declaration that uses --clip-path can override it. Then you define all variations of the property name with var(--clip-path) as their value:

* {
	--clip-path: initial;
	-webkit-clip-path: var(--clip-path);
	clip-path: var(--clip-path);
}

Then, every time we need clip-path, we use –clip-path instead and it just works:

header {
	--clip-path: polygon(0% 0%, 100% 0%, 100% calc(100% - 2.5em), 0% 100%);
}

Even !important should work, because it affects the cascading of CSS variables. Furthermore, if for some reason you want to explicitly set -webkit-clip-path, you can do that too, again because * has zero specificity. The main downside to this is that it limits browser support to the intersection of the support for the feature you are using and support for CSS Variables. However, all browsers except Edge support CSS variables, and Edge is working on it. I can’t see any other downsides to it (except having to use a different property name obvs), but if you do, let me know in the comments!

I think there’s still a lot to be discovered about cool uses of CSS variables. I wonder if there exists a variation of this technique to produce custom longhands, e.g. breaking box-shadow into --box-shadow-x, --box-shadow-y etc, but I can’t think of anything yet. Can you? 😉

  • barryvan

    This would (of course) constrain it to those situations where the prefixed property and the unprefixed property match. For ones where they don’t, you’d probably *have* to do some sort of long-hand, or use autoprefixer itself.

    I do like the fact that this approach produces more compact CSS for those cases where it works — that’s definitely worth pointing out! 🙂

    • bfredit

      It’s only apparently more compact.
      gzip should be used everywhere and “ZZZZZZ” compresses better than “abZZ” (where ZZ is the repeated definition and ab is the *{} setup)

      • mattcoz

        Yeah, if you’re only using it one time.

        • bfredit

          Still no. I tried with 8 different rules (for the same property) and still got 13% extra weight. That number won’t go down much more since regardless of how many rules you have, you still have that extra non-repeating (i.e. non-compressable) configuration as a header.

  • Not sure if this is what you meant, but this demo uses custom properties to dynamically update a single component of a longhand property: https://mobile.twitter.com/malyw/status/718219043182927872

    • That doesn’t work generically though, you cannot just use –box-shadow-color on any element and it just works.

      • WebReflection

        You could use fallbacks for numeric properties and flag one as mandatory.

        In the following example all properties beside `–box-shadow-blur` have a default fallback.

        If the `–box-shadow-radius` is not defined, the definition of the `box-shadow` itself will not be valid, aka: ignored.

        However, as soon as at least `–box-shadow-radius` is defined, you’ll see such shadow appear and you can play with the rest.

        “`css

        * {

        box-shadow: var(–box-shadow-x, 0)

        var(–box-shadow-y, 0)

        var(–box-shadow-blur) /* see this ? */

        var(–box-shadow-radius, 0)

        var(–box-shadow-color, black);

        }

        p {

        display: inline-block;

        –box-shadow-blur: 30px;

        –box-shadow-color: yellow;

        }

        p:hover {

        –box-shadow-color: orange;

        }

        “`

        • petacat

          Here’s a little demo: http://codepen.io/petacat/pen/GjJGrN

        • WebReflection

          nice one, but you can simplify further using defaults 😉

          “`
          *{
          box-shadow: var(–box-x, 0) var(–box-y, 0) var(–box-blur, 0) var(–box-scale, 0) var(–box-color, transparent);
          }

          div{
          –box-color: green;
          –box-blur: 20px;
          –box-scale: 10px;
          }
          “`

        • petacat

          Your right. I’d change the demo to use defaults (but the –box-blur).

          Is there any way to first define the box shadow and than overwrite single properties? I mean something like this:

          selector{
          –box-shadow: 10px 0 0 green;
          –box-color: blue;
          }

        • WebReflection

          * {
          –box-shadow:
          var(–box-shadow-x, 0)
          var(–box-shadow-y, 0)
          var(–box-shadow-blur) /* see this ? */
          var(–box-shadow-radius, 0)
          var(–box-shadow-color, black);
          box-shadow: var(–box-shadow, initial);
          }

          p {
          display: inline-block;
          –box-shadow-blur: 8px;
          }

          p.special {
          –box-shadow: 10px 0 0 green;
          }

        • WebReflection

          worth noting your changes make the browser render an invisible box-shadow as opposite of ignoring it if a variable is not defined. I’d still keep the blur undefined, it’s usually the most used one out there.

  • Sérgio Gomes

    Great tip! Just wanted to add that this is especially useful if you’re changing those properties in JS, for example as a result of some user event. This will keep your JS clean and blissfully unaware of prefixes 🙂

  • bfredit

    Good finding!

    Although in production prefixes are a solved problem (=> autoprefixer works better, across all browsers, and the output gzips better)

  • Pingback: Autoprefixing with CSS Variables | PSD TO WORDPRESS()

  • Pingback: Autoprefixing with CSS Variables |()

  • Pingback: Webmasters GalleryAutoprefixing with CSS Variables | Webmasters Gallery()

  • Pingback: Autoprefixing with CSS Variables - p6design.net()

  • Pingback: Autoprefixing with CSS Variables - Codango.Com()

  • Pingback: Autoprefixing, with CSS variables! | Lea Verou ...()

  • Pingback: Autoprefixing with CSS Variables - ADD_dm()

  • Pingback: Autoprefixing with CSS Variables - Browser Zone()

  • Pingback: Dew Drop - September 9, 2016 (#2325) - Morning Dew()

  • Yo Dirkx

    Doesn’t this mean every decendent of `header` also gets this `clip-path` ? header’s descendents takes header’s –clip-path, right?

    • Nope, because –clip-path: initial; is defined on the * and explicit trumps inherited in CSS.

  • Fun! This technique needs a catchy name like “Crash-cading stylesheets”. That’s a really awesome solution.

  • Pingback: Autoprefixing with CSS Variables – ThinkTanks Blog()

  • `–` means vendor prefix, so this is kind of ugly. Also, there are many better solutions for this; not sure why you’d want to use it.

  • Jakob E

    Hi Lea,

    If vendor prefixing is a thing from the past and variables the future then (sadly) I don’t
    see a great marriage….
    Saying the browsers that needs prefixes the most don’t know variables and the ones that does needs less prefixing.

    IMO The true power of CSS variables lies is their dynamic nature – making them able to separate concerns (unlike static variables found in Sass, Less etc.)…
    Saying consider what you use them for

    Example:

    // Static SCSS variables (maintainability)
    $color-1: tomato;
    $color-2: olive;
    $color-3: gold;

    // Dynamic CSS variables (separation of concerns)
    :root {
    –body-color: $color-1;
    @media (max-width: 960px){ –body-color: $color-3; }
    @media (max-width: 480px){ –body-color: $color-2; }
    }

    // Implementation (“body-color-logic”)
    body { background-color: var(–body-color); }

    Link: http://codepen.io/jakob-e/pen/RGWLdG

    Thank you for sharing all your wonderful tips, tricks and thoughts – they come in handy every day 🙂

  • Oliver Williams

    Cool article Lea. Do you use something like Post CSS or CSS Next to get this to work in older browsers?

    • You can’t, preprocessor variables are static, not dynamic. postcss+cssnext only “polyfills” the most basic case of variables.

  • Pingback: Collective #245 | PSD TO WORDPRESS()

  • Pingback: Collective #245 - ADD_dm()

  • Pingback: Collective #245 - p6design.net()

  • Pingback: Дайджест свежих материалов из мира фронтенда за последнюю неделю №227 (4 — 11 сентября 2016) - itfm.pro()

  • Pingback: Autoprefixing with CSS Variables – SourceCode()

  • Pingback: Front-End Development ( 11 September ) – Skokov()

  • Spudley

    Damn. I wish I could have been using this technique for the last ten years. Would have saved so much code. It was always obvious that CSS needed variables, even right back in the days of IE4. The purists held out against the idea for as long as they could, and the web suffered as a result.

  • 来看看,学习学习!!

  • 学习带来乐趣,谢谢博主!

  • 相当不错,自愧不如!

  • Pingback: Autoprefixing, with CSS variables! | Lea Verou ...()

  • Pingback: 【译】巧用 CSS 变量实现自动前缀!| Lea Verou - 莹莹之色()

  • Lily Lily

    Good site. Thank you so much. I found a lot of useful and interesting. I never would have thought. Good job guys prayer times

  • Folant

    Hi, Lea! content-property assepts only strings so it is impossible to use it with calc() and content: calc(4 * 5); prints nothing.

    I discovered how to convert number token to string one. And I am using counter()!!!

    .block::before {
    --a: 4;
    --b: 5;
    counter-reset: number calc(var(--a) * var(--b));
    content: "4 × 5 = " counter(number);
    }

    • This is brilliant, if you blog about this (you should), I’ll tweet the blog post!

  • Simone Amorim

    Very cool <3

    Hi, I'm simone from Recife in Brazil and tomorrow I'll going for my first talk with theme "CSS Variables – Declare variables, not war" (http://geeknightrecife.github.io/schedule/)
    I'm feeling very nervous and excitante, then I would like a quick tip about custom properties. Can you help me? tks a lot <3

  • Pingback: Make Your CSS Dynamic with CSS Custom Properties - Cible()

  • Pingback: Make your CSS dynamic with CSS Custom Properties — by Nicolas Engler()

  • Pingback: Make Your CSS Dynamic with CSS Custom Properties - wpperosna()