Serg Hospodarets Blog

Serg Hospodarets blog

CSS @apply rule (native CSS mixins) Serg Hospodarets Blog

In my previous article CSS custom properties (variables) In-Depth I described CSS custom properties (variables) and variations of their usages.

If you started thinking to move from CSS preprocessors to plain CSS after that- next your question might be: “what about mixins”?

And voilà- there is not only an editor’s draft- https://tabatkins.github.io/specs/css-apply-rule/

but even working implementation in Chrome- https://www.chromestatus.com/feature/5753701012602880

Before continuing reading, make sure you understand the terms of CSS custom properties and CSS mixins.

Defining custom sets

As we know, you can define anything as a custom properties value.

Let’s just define set of properties:

:root {
    --pink-theme: {
        color: #6A8759;
        background-color: #F64778;
    }
}

(for now even my CSS code highlighter goes crazy from such syntax so I use SCSS instead 😒)

It’s still just a valid CSS custom property only one specific think- it’s a list of CSS properties wrapped in a {} block.

Usage

To make a separation between CSS custom properties usage and mixins, was proposed to use a new at-rule CSS statement.

You are pretty familiar with many of them- these are statements which begin with an @ followed directly by one of several available keywords that acts as the identifier for what CSS should do.

Examples: @charset, @import, @keyframes, @media and many others.

So meet a new CSS statement for CSS mixins: @apply.

Let’s apply (sorry for a tautology 🙂) our list of rules:

body{
  @apply --pink-theme;
}

That’s how we easily created and used our first CSS mixin as it’s really very close to the Sass mixins idea.

So general syntax is:

// DEFINING
:root {
    --custom-property-name: {
        prop-name: value;
        /*...*/
    }
}

// APPLYING
@apply --custom-property-name;

Mixins examples

Usually each project has a number of mixins used across the codebase. Usually it’s clearfix, couple mixins to create CSS triangles and some others.

Let’s try to recreate them in plain CSS without using any preprocessors.

clearfix mixin

There are many, many, many implementations of clearfix but let’s just use the simple one:

// DEFINE
:root {
  --clearfix: {
    display: table;
    clear: both;
    content: '';
  };
}

// USE
.box:after{
  @apply --clearfix;
}

As you might check without the clearfix the browser would just “collapse” the red background.

BTW an interesting bug in the current Chrome implementation: without .box:after{content:'SOME'; before the mixin content rule from the mixin is not applied.

overflow-ellipsis mixin

When your UI requires being sure that text inside your block wouldn’t go “away” of it- one of the common solutions (depending on requirements) is usage of text-overflow rule.

But text-overflow: ellipsis; doesn’t work without overflow: hidden; and white-space: nowrap; - what a nice candidate for a mixin!

// DEFINE
:root {
  --mixin-overflow-ellipsis: {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  };
}

// USE
.overflow-box{
  @apply --mixin-overflow-ellipsis;
}

CSS triangle mixin

There are plenty ways to create simple geometric figures using CSS and one of the most popular technique is- drawing triangles using CSS borders.

Let’s create a simple mixin for it:

:root {
  --triangle-to-bottom: {
    width: 0;
    height: 0;
    border-style: solid;
    border-width: 50px 50px 0 50px;
    border-color: #007bff transparent transparent transparent;
  };
}

As you see we can provide variables for size and color. Other good idea might be to make a separate mixin for zero width/height and reuse it.

:root {
  --zero-size: {
    width: 0;
    height: 0;
  };
  
  --triangle-to-bottom-size: 50px;
  --triangle-to-bottom-color: #007bff;
  
  --triangle-to-bottom: {
    @apply --zero-size;
    border-style: solid;
    border-width: var(--triangle-to-bottom-size) var(--triangle-to-bottom-size) 0 var(--triangle-to-bottom-size);
    border-color: var(--triangle-to-bottom-color) transparent transparent transparent;
  };
}

.triangle-to-bottom {
  @apply --triangle-to-bottom;
}

Passing variables to mixins

It’s a useful practice to pass variables to mixins depending on which applied rules may vary.

Unfortunately it cannot be done for @apply rule as if you define it on a global scope (:root) it will always use variables only from that scope, so you cannot pass your local values.

It’s sad, but at least you can copy/paste your mixins to your “scopes” using e.g. preprocessor and than your local CSS variables would work.

Currently there is a discussion regarding such cases.

Browsers support and fallbacks

Browsers

  1. @apply is supported in Chrome Dev and Canary (desktop and mobile) under the chrome://flags/#enable-experimental-web-platform-features flag (issue)
  2. check a Chrome Platform Status issue to be up to date when wider support is added.

Fallbacks

Looks like the only one way so far is usage of a PostCSS plugin: https://github.com/pascalduez/postcss-apply

It enables custom properties sets references.

As you can see “The plugin is in a very early state, some features are missing” but it covers the simle cases.

Recently support was added to cssnext which also processes CSS Custom Properties.

Test browser support

Here is a copy-paste example of detecting @apply rule support in the browser:

function testCSSApply() {
  const ID = 'id' + new Date().getTime();
  
  // include styles
  const styleEl = document.createElement('style');
  styleEl.innerHTML = `
  :root {
    --${ID}: {
      font-family: ${ID};
    }
  }
  #${ID}{
    @apply --${ID};
  }
  `;
  document.head.appendChild(styleEl);

  // include element
  const el = document.createElement('i');
  el.setAttribute('id', ID)
  document.documentElement.appendChild(el);

  // test
  const styles = getComputedStyle(el);
  const doesSupport = styles.fontFamily === ID;

  // cleaning
  document.head.removeChild(styleEl);
  document.documentElement.removeChild(el);

  return doesSupport;
}

Using of it is quite simple:

if(testCSSApply()){
  document.documentElement.className += ' supported';
};

In the end

You can think “again, CSS becomes harder and harder”:

alt

But we all already got used for these conceprions from CSS preprocessors world and, on the other hand, currently we have CSS variables and mixins without any preprocessors!

Yep, syntax isn’t the best, but remember your feeling starting working with a new preprocessor.

I prefer to think about that process the same as ES6 evolving and applying:

Instead of continuing using e.g. CoffeeScript, community just started adding new features to JavaScript and now we have ~80-95% of its support in all major browsers.

So hopefully soon all that you can easily use in your projects.

Provide your code in <pre><code>...</code></pre> tags in comments